# python-docx-ng
> A Python library for creating and updating Microsoft Word (.docx) files.
python-docx-ng is a Python library for creating and updating Microsoft Word (.docx) files. It is a downstream superset of python-docx; the importable package is `docx`.
# User guide
# python-docx-ng
`python-docx-ng` is a Python library for creating and updating Microsoft Word (`.docx`) files. It is a downstream superset of [python-docx](https://github.com/python-openxml/python-docx) — the distribution is `python-docx-ng`, but the importable package is `docx`:
```
from docx import Document
```
The two distributions cannot be installed side by side.
## What it can do
```
from docx import Document
from docx.shared import Inches
document = Document()
document.add_heading("Document Title", 0)
p = document.add_paragraph("A plain paragraph having some ")
p.add_run("bold").bold = True
p.add_run(" and some ")
p.add_run("italic.").italic = True
document.add_heading("Heading, level 1", level=1)
document.add_paragraph("Intense quote", style="Intense Quote")
document.add_paragraph("first item in unordered list", style="List Bullet")
document.add_paragraph("first item in ordered list", style="List Number")
document.add_picture("monty-truth.png", width=Inches(1.25))
records = (
(3, "101", "Spam"),
(7, "422", "Eggs"),
(4, "631", "Spam, spam, eggs, and spam"),
)
table = document.add_table(rows=1, cols=3)
hdr_cells = table.rows[0].cells
hdr_cells[0].text = "Qty"
hdr_cells[1].text = "Id"
hdr_cells[2].text = "Desc"
for qty, id, desc in records:
row_cells = table.add_row().cells
row_cells[0].text = str(qty)
row_cells[1].text = id
row_cells[2].text = desc
document.add_page_break()
document.save("demo.docx")
```
## Getting started
- **[Installation](https://toxicphreak.github.io/python-docx-ng/user/install/index.md)** — install from PyPI with `pip` or `uv`.
- **[Quickstart](https://toxicphreak.github.io/python-docx-ng/user/quickstart/index.md)** — open a document, add content, save it.
- **[API reference](https://toxicphreak.github.io/python-docx-ng/api/docx/index.md)** — every module, class and property.
- **[Migrating from 0.9](https://toxicphreak.github.io/python-docx-ng/user/migrating-from-0-9/index.md)** — what changed in 2.0.0.
## For language models
The documentation is published in the [llms.txt](https://toxicphreak.github.io/python-docx-ng/llms.txt) format, with the full text at [llms-full.txt](https://toxicphreak.github.io/python-docx-ng/llms-full.txt). Point any tool that reads `llms.txt` at those URLs — for example [mcpdoc](https://github.com/langchain-ai/mcpdoc), which serves them to an editor over MCP.
# API basics
The API for `python-docx` is designed to make doing simple things simple, while allowing more complex results to be achieved with a modest and incremental investment of understanding.
It's possible to create a basic document using only a single object, the `api-Document` object returned when opening a file. The methods on `api-Document` allow *block-level* objects to be added to the end of the document. Block-level objects include paragraphs, inline pictures, and tables. Headings, bullets, and numbered lists are simply paragraphs with a particular style applied.
In this way, a document can be "written" from top to bottom, roughly like a person would if they knew exactly what they wanted to say This basic use case, where content is always added to the end of the document, is expected to account for perhaps 80% of actual use cases, so it's a priority to make it as simple as possible without compromising the power of the overall API.
## Inline objects
Each block-level method on `api-Document`, such as `add_paragraph()`, returns the block-level object created. Often the reference is unneeded; but when inline objects must be created individually, you'll need the block-item reference to do it.
... add example here as API solidifies ...
# Bookmarks
A bookmark names a span of content so something else can point at it — a cross-reference, a hyperlink, or a `PAGEREF` field asking which page it fell on.
In OOXML a bookmark is not a container. It is a pair of markers, `w:bookmarkStart` and `w:bookmarkEnd`, that can sit anywhere relative to the content between them; that is why a bookmark can span paragraphs, or half a table.
## Creating one
Over a whole paragraph:
```
from docx import Document
document = Document()
heading = document.add_heading("Introduction", level=1)
heading.add_bookmark("intro")
```
Over a narrower range, from one run through another:
```
paragraph = document.add_paragraph()
first = paragraph.add_run("the ")
middle = paragraph.add_run("important")
last = paragraph.add_run(" bit")
first.mark_bookmark_range(last, "highlight")
```
Passing the same run as both ends bookmarks just that run.
Warning
A bookmark name must be unique in the document. Word treats a duplicate as a second, separate bookmark, and the two then compete for anything that refers to the name.
## Reading them
Document.bookmarks supports `len()`, iteration, indexed access and lookup by name:
```
len(document.bookmarks)
document.bookmarks[0].name
document.bookmarks["intro"].text
document.bookmarks.get("missing") # -> None rather than KeyError
```
Each Bookmark carries:
- name and id
- text — the text it spans
- is_closed — whether the matching `w:bookmarkEnd` is present. A start without an end is malformed but does occur in the wild
- is_hidden — whether Word maintains it for itself
### Word's own bookmarks
Word keeps bookmarks of its own: `_GoBack` for the last edit position, and a `_Toc…` anchor for every heading a table of contents points at. These are hidden by default, because they are noise in almost every case:
```
for bookmark in document.bookmarks.iter_all(include_hidden=True):
print(bookmark.name)
```
## Deleting
```
document.bookmarks["intro"].delete()
```
This removes both markers and leaves the content between them alone.
## Pointing at a bookmark
Bookmarks exist to be referred to. From a field, so Word renders the target's text or page number ([Fields](https://toxicphreak.github.io/python-docx-ng/user/fields/index.md)):
```
from docx import fields
paragraph = document.add_paragraph("See ")
paragraph.add_field(fields.cross_reference("intro"))
paragraph.add_run(" on page ")
paragraph.add_field(fields.page_reference("intro"))
```
Or from a hyperlink, which jumps there when clicked:
```
paragraph = document.add_paragraph()
paragraph.add_hyperlink("back to the introduction", fragment="intro")
```
add_hyperlink() takes `address` for an external URL and `fragment` for a location inside the document; give both to link to an anchor in another document.
# Style usage and cleanup
A document created by this library defines 168 styles. A one-paragraph document references ten of them. Most of `word/styles.xml` is Word's built-in gallery, carried along because the template it came from carried it.
This page is about finding that out and doing something about it. The command-line front end for everything here is [`python -m docx`](https://toxicphreak.github.io/python-docx-ng/user/cli/index.md).
## Which styles are in use
```
usage = document.styles.usage()
print(usage)
# 168 styles defined, 10 in use, 158 unused; 131 latent style exceptions
```
Styles.usage() returns a StyleUsage, a named tuple of style **ids**:
| Field | Meaning |
| ------------------ | ------------------------------------------------------------------------------------------- |
| `defined` | every style id in the styles part, in document order |
| `used` | the ids in the reachability closure of what the document applies |
| `unused` | `defined` minus `used`, in document order |
| `reference_counts` | how many times each id is *directly* applied; a style reachable only indirectly counts zero |
| `latent` | names declared in `w:latentStyles` that the document does not define |
Iterating a `StyleUsage` yields the ids in use.
The convenience accessors work in names, as the rest of the styles API does:
```
for style in document.styles.unused:
print(style.name)
document.styles["Heading 7"].in_use # -> False
```
### "Used" is a closure, not a scan
A scan of `w:pStyle` in `word/document.xml` gets the wrong answer in four ways, and each of them is a real document:
- **Every story part counts, not just the body.** Headers, footers, footnotes, endnotes and comments are separate parts with their own style references.
- **A style can be reachable without ever being applied** — as the `w:basedOn` of a used style, as its `w:next`, as its `w:link`, from a numbering level's `w:pStyle`, or from the `w:tblStylePr` conditional formatting inside a table style.
- **The `w:default="1"` styles apply to content that names no style at all.** They are used by definition and have zero direct references.
- **`Normal` is never dead.** Word repairs a document that lacks it, and the repair dialogue is worse than the bloat.
So "used" is computed as a reachability closure: seed from the direct applications, the default styles and `Normal`, then follow the reference edges until the set stops growing. A dangling edge — a `w:basedOn` naming a style that is not defined — is a dead end rather than an error, because that is legal and common.
Two keyword arguments adjust the seed:
```
# -- treat a style you are about to apply as used, along with its dependencies --
document.styles.usage(keep=("Quote",))
# -- the narrower question: what is reachable by reference alone? --
document.styles.usage(seed_defaults=False)
```
## Removing the unused ones
```
removed = document.styles.remove_unused()
len(removed) # -> 158
```
Styles.remove_unused() returns the ids it removed. It takes the same `keep`, and `keep_defaults` (`True` by default) protects the `w:default="1"` styles.
Warning
This is destructive and it is not undoable within the open document. A style you intend to apply later is unused *now*, so name it in `keep`:
```
document.styles.remove_unused(keep=("Quote", "Intense Quote"))
```
## Cleaning up the whole document
Document.cleanup() runs three passes and returns a CleanupResult:
```
result = document.cleanup()
print(result)
# removed 158 styles, 3 numbering definitions, 3 abstract numbering
# definitions, 0 media parts and 0 latent style exceptions
result.styles # -> the style ids
result.num_ids # -> the w:num numIds
result.abstract_num_ids # -> the w:abstractNum abstractNumIds
result.media # -> the partnames of the image parts
result.latent_styles # -> how many w:lsdException overrides went
```
The three passes, each of which can be turned off:
**Styles** — the closure above.
**Numbering definitions** — a `w:num` no `w:numPr` points at is dead, and a `w:abstractNum` no surviving `w:num` points at is dead too. Chains through `w:numStyleLink` are followed, so a definition kept alive only indirectly survives.
**Orphan media** — an image part related from nothing, which is what .delete() on a paragraph holding a picture leaves behind. Only the document part's own image relationships are considered: a header image belongs to the header part and is not orphaned by anything happening in the body.
```
document.cleanup(
styles=True,
numbering=True,
media=True,
latent_styles=False, # -- off by default, see below --
keep=("Quote",),
)
```
Styles are pruned before numbering, so a numbering definition kept alive only by a style that is about to go is correctly seen as dead.
### Latent styles are separate on purpose
`latent_styles` is `False` by default. A `w:lsdException` is a *behaviour declaration* for a style the document does not define — whether it shows in Word's style gallery, and in what order. Removing one changes what a user sees in the UI rather than how the document renders, which is a different kind of change from removing a style definition.
LatentStyles.trim() does it on its own, and returns how many went. It removes *every* override — the bundled template carries 137, one per built-in Word might offer — while leaving the defaults on the `w:latentStyles` element itself, which are what the overrides were overriding:
```
document.styles.latent_styles.trim() # -> 137
```
That count is not the same as `usage.latent`, which is the narrower set of latent names the document does not also *define*. The two answer different questions and it is worth not confusing them.
## What this is worth
For a document generated from the bundled template and then cleaned:
| | Uncompressed | On disk |
| ----------------- | ------------ | ------- |
| As generated | 376 KB | 20 KB |
| After `cleanup()` | 40 KB | 9 KB |
Almost all of it is `word/styles.xml`. Whether that matters depends on what you are doing — it is nothing for one document and a great deal for a hundred thousand of them.
## Moving styles between documents
The other side of the same coin: rather than pruning what a template brought, bring only what you want. See [Templates and embedded files](https://toxicphreak.github.io/python-docx-ng/user/templates/#importing-a-templates-styles).
# Command line
`python -m docx` answers the questions people ask about a `.docx` one at a time: what styles it defines, which of them are actually used, and why the file is 900 KB.
It is deliberately thin. Every subcommand maps onto one public library operation and holds no logic of its own, so there is nothing reachable through the command line that is not reachable from Python. There is no new dependency — it is `argparse` only.
```
$ python -m docx --help
usage: python -m docx [-h] {info,styles,cleanup} ...
Inspect and clean up Word documents.
positional arguments:
{info,styles,cleanup}
info list the parts of a document with their sizes
styles inspect a document's styles
cleanup remove unused styles, numbering and media
```
## `info` — what is in the package
The fastest answer to "why is this file like this": the part inventory, biggest first.
```
$ python -m docx info report.docx
350527 word/styles.xml
7642 word/theme/theme1.xml
5513 word/numbering.xml
2811 word/fontTable.xml
2535 word/settings.xml
1586 word/document.xml
...
375746 TOTAL uncompressed (20199 on disk)
```
This is how the 438 KB `word/stylesWithEffects.xml` that used to ship in the bundled template turned up. `--json` emits the same data for a script to consume.
## `styles` — what it defines and what it uses
```
$ python -m docx styles report report.docx
168 styles defined (30 character, 1 list, 37 paragraph, 100 table)
10 in use, 158 unused
131 latent style exceptions
```
"In use" is a reachability closure over every story part, not a scan of the body — see [Style usage and cleanup](https://toxicphreak.github.io/python-docx-ng/user/cleanup/index.md) for what that means and why the distinction matters.
`styles list` prints the names, optionally filtered:
```
$ python -m docx styles list report.docx --used
Normal
Default Paragraph Font
Normal Table
No List
List Bullet
...
```
`--used` and `--unused` are mutually exclusive; asking for both would match nothing.
`styles extract` writes the styles out to a document of their own, which is how you turn a document you like the look of into a template:
```
$ python -m docx styles extract house-style.docx -o house.dotx --as-template
$ python -m docx styles extract report.docx -o headings.docx --names "Heading 1,Heading 2"
```
Named styles come with their `basedOn` / `next` / `link` closure, so the extract is a document that opens without repair.
## `cleanup` — remove what nothing points at
```
$ python -m docx cleanup report.docx -o small.docx
removed 158 styles, 3 numbering definitions, 3 abstract numbering definitions, 0 media parts and 0 latent style exceptions
wrote small.docx
```
Two rules this command keeps:
**It never writes to its input.** `-o` is required. Someone will point it at their only copy.
**`--check` is a CI gate.** It reports what *would* be removed and exits non-zero if anything would be, so a pipeline can fail a build that has grown dead weight:
```
$ python -m docx cleanup report.docx --check
would have removed 158 styles, 3 numbering definitions, ...
$ echo $?
1
```
| Flag | Effect |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `--keep NAMES` | comma-separated style names to preserve, along with their dependencies |
| `--no-styles` | leave the style definitions alone |
| `--no-numbering` | leave the numbering definitions alone |
| `--no-media` | leave orphaned image parts alone |
| `--latent-styles` | *also* drop the latent-style exceptions — off by default, because this changes what a user sees in Word's style gallery |
| `--json` | emit the full list of what went, not just the counts |
## Exit codes
These end up in scripts, so they are part of the contract:
| Code | Meaning |
| ---- | ---------------------------------------------------------------------------------- |
| `0` | success |
| `1` | `cleanup --check` found something to remove |
| `2` | the document could not be opened, or an argument named something that is not there |
A document that cannot be opened is reported as a message on stderr, never as a traceback — including the password-protected case, which is a different thing from a corrupt file:
```
$ python -m docx info nope.docx
error: cannot open document: Package not found at 'nope.docx'
```
# Working with Comments
Word allows *comments* to be added to a document. This is an aspect of the *reviewing* feature-set and is typically used by a second party to provide feedback to the author without changing the document itself.
The procedure is simple:
- You select some range of text with the mouse or Shift+Arrow keys
- You press the *New Comment* button (Review toolbar)
- You type or paste in your comment
A comment can only be added to the main document. A comment cannot be added in a header, a footer, or within a comment. A comment can\_ be added to a footnote or endnote, but those are not yet supported by *python-docx*.
**Comment Anatomy.** Each comment has two parts, the *comment-reference* and the *comment-content*:
The **comment-refererence**, sometimes *comment-anchor*, is the text in the main document you selected before pressing the *New Comment* button. It is a so-called *range* in the main document that starts at the first selected character and ends after the last one.
The **comment-content**, sometimes just *comment*, is whatever content you typed or pasted in. The content for each comment is stored in a separate comment object, and these comment objects are stored in a separate *comments-part* (part-name `word/comments.xml`), not in the main document. Each comment is assigned a unique id when it is created, allowing the comment reference to be associated with its content and vice versa.
**Comment Reference.** The comment-reference is a *range*. A range must both start and end at an even *run* boundary. Intuitively, a range corresponds to a *selection* of text in the Word UI, one formed by dragging with the mouse or using the *Shift-Arrow* keys.
In the XML, this range is delimited by a start marker *\* and an end marker *\*, both of which contain the *id* of the comment they delimit. The start marker appears before the run starting with the first character of the range and the end marker appears immediately after the run ending with the last character of the range. Adding a comment that references an arbitrary range of text in an existing document may require splitting runs on the desired character boundaries.
In general a range can span paragraphs, such that the range begins in one paragraph and ends in a later paragraph. However, a range must enclose *contiguous* runs, such that a range that contains only two vertically adjacent cells in a multi-column table is not possible (even though Word allows such a selection with the mouse).
**Comment Content.** Interestingly, although commonly used to contain a single line of plain text, the comment-content can contain essentially any content that can appear in the document body. This includes rich text with emphasis, runs with a different typeface and size, both paragraph and character styles, hyperlinks, images, and tables. Note that tables do not appear in the comment as displayed in the *comment-sidebar* although they do apper in the *reviewing-pane*.
**Comment Metadata.** Each comment can be assigned *author*, *initals*, and *date* metadata. In Word, these fields are assigned automatically based on values in `Settings > User` of the installed Word application. These might be configured automatically in an enterprise installation, based on the user account, but by default they are empty.
*author* metadata is required, although silently assigned the empty string by Word if the user name is not configured. *initials* is optional, but always set by Word, to the empty string if not configured. *date* is also optional, but always set by Word to the UTC date and time the comment was added, with seconds resolution (no milliseconds or microseconds).
**Additional Features.** Later versions of Word allow a comment to be *resolved*. A comment in this state will appear grayed-out in the Word UI. Later versions of Word also allow a comment to be *replied to*, forming a *comment thread*. Neither of these features is supported by the initial implementation of comments in *python-docx*.
**Applicability.** Note that comments cannot be added to a header or footer and cannot be nested inside a comment itself. In general the *python-docx* API will not allow these operations but if you outsmart it then the resulting comment will either be silently removed or trigger a repair error when the document is loaded by Word.
## Adding a Comment
A simple example is adding a comment to a paragraph:
```
>>> from docx import Document
>>> document = Document()
>>> paragraph = document.add_paragraph("Hello, world!")
>>> comment = document.add_comment(
... runs=paragraph.runs,
... text="I have this to say about that"
... author="Steve Canny",
... initials="SC",
... )
>>> comment
>>> comment.id
0
>>> comment.author
'Steve Canny'
>>> comment.initials
'SC'
>>> comment.date
datetime.datetime(2025, 6, 11, 20, 42, 30, 0, tzinfo=datetime.timezone.utc)
>>> comment.text
'I have this to say about that'
```
The API documentation for Document.add_comment provides further details.
## Accessing and using the Comments collection
The comments collection is accessed via the Document.comments property:
```
>>> comments = document.comments
>>> comments
>>> len(comments)
1
```
The comments collection supports random access to a comment by its id:
```
>>> comment = comments.get(0)
>>> comment
```
## Adding rich content to a comment
A comment is a block-item container, just like the document body or a table cell, so it can contain any content that can appear in those places. It does not contain page-layout sections and cannot contain a comment reference, but it can contain multiple paragraphs and/or tables, and runs within paragraphs can have emphasis such as bold or italic, and have images or hyperlinks.
A comment created with *text=""* will contain a single paragraph with a single empty run containing the so-called *annotation reference* but no text. It's probably best to leave this run as it is but you can freely add additional runs to the paragraph that contain whatever content you like.
The methods for adding this content are the same as those used for the document and table cells:
```
>>> paragraph = document.add_paragraph("The rain in Spain.")
>>> comment = document.add_comment(
... runs=paragraph.runs,
... text="",
... )
>>> cmt_para = comment.paragraphs[0]
>>> cmt_para.add_run("Please finish this thought. I believe it should be ")
>>> cmt_para.add_run("falls mainly in the plain.").bold = True
```
## Updating comment metadata
The author and initials metadata can be updated as desired:
```
>>> comment.author = "John Smith"
>>> comment.initials = "JS"
>>> comment.author
'John Smith'
>>> comment.initials
'JS'
```
# Copying content
`copy_to()` duplicates a paragraph, a run, a table row or a whole table — into the same document or a different one.
```
paragraph.copy_to(document)
row.copy_to(table)
table.copy_to(other_document)
run.copy_to(paragraph)
```
## Why not `copy.deepcopy()`
Duplicating content is the operation most often written by hand against this library, and the hand-written version works for plain text and quietly breaks for anything interesting. `copy_to()` is a deep copy plus a repair for each of these:
- **A picture's `r:embed`** names a relationship id belonging to the *source* part, so a copied image is either the wrong image or a dangling reference.
- **A hyperlink's `r:id`** has the same problem, and points at an external target that may not exist in the destination package.
- **`wp:docPr/@id`** must be unique document-wide, and a deep copy duplicates it.
- **A bookmark name** is document-wide, and a duplicate is not a copy of a bookmark: Word treats it as a second bookmark of the same name, and anything referring to that name resolves to whichever it finds first.
- **Across documents**, a `w:pStyle` names a style that may not be there and a `w:numPr` names a `numId` that certainly means something else.
Relating the same image into the destination reuses the sha1 deduplication, so copying a picture into a document that already has it does not add a second copy of the bytes.
## Where the copy goes
Appended to the end of the container by default; `before` and `after` place it:
```
paragraph.copy_to(document) # -> at the end of the body
paragraph.copy_to(document, before=document.paragraphs[0])
paragraph.copy_to(cell) # -> into a table cell
row.copy_to(table, after=table.rows[0])
```
Appending to a body that ends in a `w:sectPr` inserts before it, since the section properties have to stay last. Passing both `before` and `after` raises `ValueError`.
Each returns a proxy for the copy, so a repeated row is one line:
```
template_row = table.rows[0]
for record in records:
new_row = template_row.copy_to(table)
new_row.cells[0].text = record.name
```
## Copying into another document
A cross-document copy brings what the content refers to with it. `missing_style` decides what happens to a style the destination does not define:
| Value | Behaviour |
| ------------------ | ------------------------------------------------------------------------------- |
| `"copy"` (default) | copy the style across with its `basedOn` / `next` / `link` closure |
| `"drop"` | remove the style reference; the content falls back to the destination's default |
| `"raise"` | raise `ValueError` |
```
paragraph.copy_to(other_document, missing_style="drop")
```
A style reference the *source* cannot resolve either is left alone — it is already broken there, and inventing a target would be worse than carrying the break over.
Numbering comes across too, and this is the part that is easy to get wrong by hand. A `w:numId` means something else in the destination, so leaving it alone numbers the copied paragraph according to whichever list happens to hold that id — a silent wrong answer rather than a visible failure. The definition is copied and the copy repointed at it, and the source's `w:nsid` is dropped so the destination's list gallery does not show two definitions as the same list.
## Bookmarks are dropped, not renamed
```
paragraph.add_bookmark("Target")
copy = paragraph.copy_to(document)
[b.name for b in document.bookmarks] # -> ["Target"], not two of them
```
This is the one place `copy_to()` deliberately loses something. Renaming would leave a bookmark nothing points at; keeping the name would leave two competing for every reference to it. Dropping is the only outcome that is not silently wrong — use Paragraph.add_bookmark() on the copy to bookmark it afresh.
# Document properties
Word keeps three separate sets of properties, in three parts of the package. They are not interchangeable, and which one you want depends on who wrote the value.
| | Part | Written by |
| -------------------------------- | --------------------- | --------------------------------- |
| [Core](#core-properties) | `docProps/core.xml` | you, and Word's File → Info panel |
| [Extended](#extended-properties) | `docProps/app.xml` | Word, mostly statistics |
| [Custom](#custom-properties) | `docProps/custom.xml` | you, under any name you like |
## Core properties
The standard Dublin Core set — the fields Word shows under File → Info:
```
from docx import Document
document = Document()
core = document.core_properties
core.title = "Quarterly report"
core.author = "Ada Lovelace"
core.subject = "Finance"
core.keywords = "quarterly, finance, 2026"
core.comments = "Draft for review"
core.category = "Report"
core.content_status = "Draft"
core.language = "en-GB"
```
The date fields take a `datetime`:
```
import datetime as dt
core.created = dt.datetime(2026, 3, 14, 9, 0)
core.modified = dt.datetime.now()
```
Also available: `identifier`, `last_modified_by`, `last_printed`, `revision` (an `int`) and `version`.
Unset string properties read as `""` rather than `None`, and unset dates as `None`. The date properties accept only a `datetime` on assignment — assigning `None` to clear one raises `ValueError`.
## Extended properties
`docProps/app.xml` is mostly Word's own bookkeeping — how many pages, words and characters the document had when Word last saved it:
```
extended = document.extended_properties
extended.pages
extended.words
extended.characters
extended.paragraphs
extended.lines
extended.total_time # editing minutes
extended.application # e.g. "Microsoft Office Word"
extended.app_version
```
Warning
These statistics are whatever Word wrote when it last saved. This library does not recompute them, so a document you have edited here reports the old counts. Treat them as a record of Word's last visit, not as a live measure.
A few are yours to set, and Word displays them:
```
extended.company = "Analytical Engines Ltd"
extended.manager = "Charles Babbage"
extended.hyperlink_base = "https://example.com/docs/"
```
`template` names the template the document was created from.
Note
`CT_Properties` is an `xsd:all` type, so Word writes these children in an order that does not match the order the schema lists them in. That is expected, and nothing in the API depends on their order.
## Custom properties
`docProps/custom.xml` holds properties under any name you choose, which is what makes it useful for carrying your own metadata through a document. It behaves as a `dict`:
```
import datetime as dt
props = document.custom_properties
props["Matter number"] = 4242
props["Reviewed"] = True
props["Reviewed on"] = dt.datetime(2026, 3, 14)
props["Rate"] = 1.5
props["Client"] = "Analytical Engines Ltd"
props["Matter number"] # -> 4242
"Client" in props # -> True
len(props)
list(props) # -> the names
del props["Reviewed"]
```
A value may be a `str`, `int`, `float`, `bool` or `datetime`; those are the variant types Word writes, and each reads back as the same Python type. Assigning anything else raises `ValueError` rather than writing a file Word would refuse to open.
Names are unique and case-sensitive, and may contain spaces.
### Showing one in the document
A `DOCPROPERTY` field puts a custom property's value into the text, so it updates wherever it appears when the property changes ([Fields](https://toxicphreak.github.io/python-docx-ng/user/fields/index.md)):
```
from docx import fields
paragraph = document.add_paragraph("Matter: ")
paragraph.add_field(fields.doc_property("Matter number"))
```
## The custom XML data store
A different thing from the properties above, and easy to confuse with them. The three kinds of properties are flat named scalars — a string, a number, a date. The **custom XML data store** holds arbitrary XML documents, each in a part of its own (`customXml/item1.xml`, with an `itemProps1.xml` sidecar declaring its schemas).
This is where a document-generation pipeline keeps the structured data its content controls are bound to. Word's data binding points a content control at an XPath into one of these items, so the control's displayed text and the stored data stay the same thing.
```
part = document.add_custom_xml_part(
""
"2026-0141450.00"
"",
schema_refs=("urn:example:invoice",),
)
part.item_id # -> "{...}", the GUID Word identifies the item by
part.schema_refs # -> ("urn:example:invoice",)
part.xml # -> the XML as text
```
Reading them back:
```
for part in document.custom_xml_parts:
print(part.partname, part.item_id, part.schema_refs)
print(part.xml)
```
Document.custom_xml_parts is a tuple in relationship order, empty for a document that has no data store. add_custom_xml_part() accepts a `str` or `bytes`, allocates the next free partname, and generates the `w:itemProps` sidecar with a GUID of its own — Word requires the sidecar, and an item without one is a repair prompt.
`schema_refs` names the namespaces the item uses. It is optional and it is what Word's XML mapping pane lists, so supplying it is what makes the item usable for data binding through the UI.
Warning
**The generated GUID is the one thing in this library's output that is not a function of its input.** Everything else about a saved document is deterministic — see [Reproducible output](https://toxicphreak.github.io/python-docx-ng/user/documents/#reproducible-output). If that matters to you, supply the `item_id`:
```
document.add_custom_xml_part(xml, item_id="{...}")
```
It only has to be unique within the document.
Note
A document created by this library carries **no** data store. The bundled template used to ship an empty bibliography item left over from its author's Word session, which meant `custom_xml_parts` reported a store the caller had never added. That is gone as of 2.1.0.
# Working with Documents
`python-docx` allows you to create new documents as well as make changes to existing ones. Actually, it only lets you make changes to existing documents; it's just that if you start with a document that doesn't have any content, it might feel at first like you're creating one from scratch.
This characteristic is a powerful one. A lot of how a document looks is determined by the parts that are left when you delete all the content. Things like styles and page headers and footers are contained separately from the main content, allowing you to place a good deal of customization in your starting document that then appears in the document you produce.
Let's walk through the steps to create a document one example at a time, starting with two of the main things you can do with a document, open it and save it.
## Opening a document
The simplest way to get started is to open a new document without specifying a file to open:
```
from docx import Document
document = Document()
document.save('test.docx')
```
This creates a new document from the built-in default template and saves it unchanged to a file named 'test.docx'. The so-called "default template" is actually just a Word file having no content, stored with the installed `python-docx` package. It's roughly the same as you get by picking the *Word Document* template after selecting Word's **File > New from Template...** menu item.
## REALLY opening a document
If you want more control over the final document, or if you want to change an existing document, you need to open one with a filename:
```
document = Document('existing-document-file.docx')
document.save('new-file-name.docx')
```
Things to note:
- You can open any Word 2007 or later file this way (`.doc` files from Word 2003 and earlier won't work). Macro-enabled `.docm` files and `.dotx` / `.dotm` templates open too.
- Not every part of a document has an API yet — charts and SmartArt diagrams, for instance. Anything without one is left untouched and written back out unchanged on save, so opening and re-saving a document never silently discards content it does not understand.
- If you use the same filename to open and save the file, `python-docx` will obediently overwrite the original file without a peep. You'll want to make sure that's what you intend.
## Files that cannot be opened
A few kinds of file look like an ordinary `.docx` and are not one. Each raises something that says which:
| Raised | Meaning |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PackageNotFoundError` | No file at that path, or the file is not a readable OPC package — a truncated download, or not a zip archive at all. |
| `EncryptedPackageError` | The document is password-protected. It is an OLE compound file wrapping the encrypted package, so it cannot be read without the password. Subclasses `PackageNotFoundError`. |
| `StrictOoxmlNotSupportedError` | The document was saved as **Strict Open XML Document**, an option in Word's Save As dialogue and the default in some regulated environments. It uses the same element names in the ISO Strict namespaces rather than the Transitional ones this library reads. Re-saving from Word as "Word Document (.docx)" converts it. |
| `ValueError` | The package is a valid OPC package but not a Word one — a `.xlsx` or `.pptx`, for instance. |
## Opening a 'file-like' document
`python-docx` can open a document from a so-called *file-like* object. It can also save to a file-like object. This can be handy when you want to get the source or target document over a network connection or from a database and don't want to (or aren't allowed to) interact with the file system. In practice this means you can pass an open file or StringIO/BytesIO stream object to open or save a document like so:
```
f = open('foobar.docx', 'rb')
document = Document(f)
f.close()
# or
with open('foobar.docx', 'rb') as f:
source_stream = StringIO(f.read())
document = Document(source_stream)
source_stream.close()
...
target_stream = StringIO()
document.save(target_stream)
```
The `'rb'` file open mode parameter isn't required on all operating systems. It defaults to `'r'` which is enough sometimes, but the 'b' (selecting binary mode) is required on Windows and at least some versions of Linux to allow Zipfile to open the file.
Okay, so you've got a document open and are pretty sure you can save it somewhere later. Next step is to get some content in there ...
## Reproducible output
The same document data serialises to the same bytes, every time, on any machine. Three things make that true:
- Every zip member is stamped with a fixed timestamp — the zip epoch, 1980-01-01 — rather than the time of the save.
- Parts are written in partname order.
- Relationships are written in numeric `rId` order.
So a build that generates a document twice produces two identical files, and a checksum is a meaningful thing to compare. `tests/test_reproducible.py` pins this.
The one exception is add_custom_xml_part(), which generates a random GUID unless you supply one — see [The custom XML data store](https://toxicphreak.github.io/python-docx-ng/user/document-properties/#the-custom-xml-data-store).
Anything you put in a document yourself is of course yours to keep deterministic. A timestamp in [core properties](https://toxicphreak.github.io/python-docx-ng/user/document-properties/index.md), or text built from `datetime.now()`, changes the input and therefore the output.
## Reducing the size of what you produce
A document generated from the bundled template is around 20 KB on disk, and most of that is the 168 style definitions Word's own template carries. `Document.cleanup()` removes what nothing points at and takes it to about 9 KB:
```
document.cleanup()
```
See [Style usage and cleanup](https://toxicphreak.github.io/python-docx-ng/user/cleanup/index.md), and [`python -m docx cleanup`](https://toxicphreak.github.io/python-docx-ng/user/cli/index.md) for doing it to a file you already have.
## Parts beyond the body
A `.docx` is a package of parts, and several of them have an API of their own. The ones not covered by another page:
| | |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Document.theme | the theme fonts and colours — see [Theme fonts](https://toxicphreak.github.io/python-docx-ng/user/text/#theme-fonts) |
| Document.custom_xml_parts | the custom XML data store — see [Document properties](https://toxicphreak.github.io/python-docx-ng/user/document-properties/#the-custom-xml-data-store) |
| Document.vba_project | the macro project — see [Macros](https://toxicphreak.github.io/python-docx-ng/user/templates/#macros) |
| Document.embedded_objects | embedded OLE objects — see [Embedded OLE objects](https://toxicphreak.github.io/python-docx-ng/user/templates/#embedded-ole-objects) |
| Document.images | the images the body embeds — see [Images](https://toxicphreak.github.io/python-docx-ng/user/images/#reading-the-images-already-in-a-document) |
| Document.math | the equations — see [Equations](https://toxicphreak.github.io/python-docx-ng/user/text/#equations) |
| Document.endnotes | the endnotes — see [Footnotes](https://toxicphreak.github.io/python-docx-ng/user/footnotes/#endnotes) |
`Document.part.package.iter_parts()` reaches everything, typed where a part class exists and as a plain `Part` where one does not.
# Fields and tables of contents
A field is how Word represents anything it works out for itself: page numbers, a table of contents, cross-references, captions that renumber, dates, and references to document properties. Every one of those is the same feature with a different instruction string.
Warning
**This library cannot compute a field result, and no API can change that.** A table of contents added here is empty, a `PAGE` field has no number, and a cross-reference shows nothing, because all three depend on how Word lays the document out. Fields are written asking Word to refresh them when it opens the file — see [Getting Word to fill them in](#getting-word-to-fill-them-in).
## Adding a field
Paragraph.add_field() takes a field code. The builders in docx.fields write the ones people usually want, so you rarely need to remember the switch syntax:
```
from docx import Document, fields
document = Document()
paragraph = document.add_paragraph("Page ")
paragraph.add_field(fields.page_number())
paragraph.add_run(" of ")
paragraph.add_field(fields.page_count())
```
The builders return the instruction string, not the field, so they compose with anything that takes a field code:
| Builder | Field |
| ------------------- | ------------------------------------ |
| page_number() | `PAGE` — the current page |
| page_count() | `NUMPAGES` — pages in the document |
| table_of_contents() | `TOC` |
| cross_reference() | `REF` — text of a bookmark |
| page_reference() | `PAGEREF` — page a bookmark is on |
| sequence() | `SEQ` — caption and figure numbering |
| date() | `DATE` |
| doc_property() | `DOCPROPERTY` |
| styleref() | `STYLEREF` — nearest text in a style |
An instruction Word understands but no builder covers can be passed directly:
```
paragraph.add_field(r'INCLUDEPICTURE "logo.png" \d')
```
## A table of contents
```
from docx import Document, fields
document = Document()
document.add_paragraph("Contents", style="Heading 1")
toc = document.add_paragraph()
toc.add_field(fields.table_of_contents(levels=(1, 3)))
document.add_page_break()
document.add_heading("Introduction", level=1)
document.add_paragraph("...")
document.settings.update_fields_on_open = True
document.save("with-toc.docx")
```
`levels` is the heading range to include. The document opens showing "Update this document?" and the contents appear once Word rebuilds them.
## Cross-references
A cross-reference points at a bookmark, so bookmark the target first:
```
from docx import Document, fields
document = Document()
intro = document.add_heading("Introduction", level=1)
intro.add_bookmark("intro")
body = document.add_paragraph("See ")
body.add_field(fields.cross_reference("intro"))
body.add_run(" on page ")
body.add_field(fields.page_reference("intro"))
```
cross_reference() writes the bookmark's *text* and page_reference() the *page number* it falls on. See [Bookmarks](https://toxicphreak.github.io/python-docx-ng/user/bookmarks/index.md) for the ways to create one.
## Captions
Document.add_caption() writes the whole thing — the label, the self-renumbering `SEQ` field, and the bookmark a cross-reference needs:
```
document.add_picture("architecture.png")
caption = document.add_caption("Figure", "the architecture")
```
That gives a paragraph in the "Caption" style that Word renders as "Figure 1 the architecture", where the number is a field it recomputes whenever figures are inserted or removed. Read back before Word has computed it, the number is simply missing: `caption.text` is `"Figure the architecture"`.
```
caption.paragraph # -> the Paragraph, for any further formatting
caption.label # -> "Figure"
caption.text # -> the text as the document currently reads it
caption.number # -> the number Word last computed, or None
caption.bookmark_name # -> "_Ref000000001"
```
`number` is `None` for a caption this library has just written: a `SEQ` field's result is cached in the document, so it reads back only after a round trip through Word.
Referring to it later is one call:
```
document.add_paragraph("See ").add_field(
fields.cross_reference(caption.bookmark_name)
)
```
**The bookmark name is the part that matters.** Word's cross-reference dialogue offers only targets whose bookmark name follows its own `_Ref` convention, so a caption bookmarked with an arbitrary name works but is invisible in the UI — the user cannot reference it. `add_caption()` generates names in that shape. (Word derives its own from a timestamp; a counter is used here instead, because a timestamp would make the same document generate different bytes on each run.) That shape also means the bookmark does not appear in Document.bookmarks, which leaves out the ones Word maintains for itself; `Bookmarks.iter_all()` reaches it.
Four keyword arguments adjust the result:
```
document.add_caption("Table", "Revenue by region", separator=": ")
document.add_caption("Figure", "A cat", restart_at_heading_level=1) # -> "Figure 3-2"
document.add_caption("Figure", "A cat", before=document.paragraphs[0])
document.add_caption("Figure", "A cat", style=None) # -> no style applied
```
`separator` is what goes between the number and the text. `restart_at_heading_level` starts the numbering over at each chapter, which is the "Figure 3-2" convention. \_Cell.add_caption() does the same inside a table cell.
Each label is its own sequence, so figures, tables and equations number independently.
### Assembling one by hand
`SEQ` is the counter underneath, and it is still there if you want to build a caption yourself:
```
caption = document.add_paragraph("Figure ", style="Caption")
caption.add_field(fields.sequence("Figure"))
caption.add_run(": the architecture")
```
```
caption.add_field(fields.sequence("Figure", restart_at_heading_level=1))
```
This is what `add_caption()` does, minus the bookmark.
## Reading the fields in a document
Document.fields returns every field in the body, and Paragraph.fields the fields of one paragraph:
```
for field in document.fields:
print(field.type, field.instruction, repr(field.result_text))
```
- Field.instruction — the field code, switches and all
- Field.type — the first word of it, e.g. `"PAGE"`
- Field.result_text — the cached result Word last computed, which is what a reader saw
- Field.is_simple — whether Word stored it as a self-contained `w:fldSimple` or spread it across runs
A field's cached result counts towards Paragraph.text, so the page number a reader saw is part of the text you read back.
Complex fields nest — a `TOC` result is full of `PAGEREF` fields — and each inner field is a field in its own right.
## Getting Word to fill them in
Two things ask Word to compute results, and they are not the same:
```
document.settings.update_fields_on_open = True
```
Settings.update_fields_on_open sets a document-wide flag; Word prompts on open and refreshes every field. This is what a generated table of contents needs.
Individual fields are written with `w:dirty` set, which asks Word to refresh that field alone. Pass `dirty=False` to add_field() to suppress it.
To keep the field from showing as blank before the first refresh, supply cached result text of your own. That requires `simple=True`, because only a `w:fldSimple` stores its result as plain content — a complex field's result is whatever sits between its `separate` and `end` field characters, which Word writes when it computes it:
```
paragraph.add_field(fields.page_number(), simple=True, result="1")
```
# Footnotes
A footnote lives in its own part, `word/footnotes.xml`, and the document body carries only a reference to it. Creating one is therefore two steps: add the footnote, then reference it from a run.
## Adding a footnote
```
from docx import Document
document = Document()
paragraph = document.add_paragraph("The engine was never built")
footnote = document.footnotes.add_footnote("Babbage, 1837.")
paragraph.runs[-1].add_footnote_reference(footnote)
```
add_footnote_reference() appends the reference mark to the end of the run, so which run you call it on decides where the superscript number appears.
A footnote can hold more than the one paragraph its text argument creates:
```
footnote = document.footnotes.add_footnote("See also:")
footnote.add_paragraph("Menabrea, 1842.")
footnote.add_paragraph("Lovelace, Note G.", style="Quote")
```
## Reading them
Document.footnotes supports `len()` and iteration, and looks a footnote up by id:
```
len(document.footnotes)
for footnote in document.footnotes:
print(footnote.footnote_id, footnote.text)
document.footnotes.get(2) # -> the Footnote, or None
```
Note
Word reserves the first two ids for the separator and continuation separator — the little rules drawn above footnote text. Iterating skips those, so what you get is the footnotes a reader would count.
Numbering is Word's to compute, as with any field: `footnote_id` is an identifier, not the number printed in the margin.
Note
The API changed in 2.0.0. The 0.9.x line had `Paragraph.add_footnote()`; footnotes are now Document.footnotes plus Run.add_footnote_reference(), which separates creating the note from placing the mark.
## Endnotes
Endnotes are the same shape, one part over: they live in `word/endnotes.xml` and collect at the end of the document or section rather than at the foot of the page.
```
paragraph = document.add_paragraph("The engine was never built")
endnote = document.endnotes.add_endnote("Babbage, 1837.")
paragraph.runs[-1].add_endnote_reference(endnote)
```
Document.endnotes supports `len()`, iteration and lookup by id, exactly as `footnotes` does, and an Endnote has `.text`, `.endnote_id` and `.add_paragraph()`:
```
for endnote in document.endnotes:
print(endnote.endnote_id, endnote.text)
document.endnotes.get(1) # -> the Endnote, or None
```
`word/endnotes.xml` is created on demand the first time you add one, as the footnotes part is, so a document that has no endnotes carries no endnotes part.
Note
As in the footnotes part, ids `-1` and `0` are taken by the separator and continuation separator — the rules drawn above endnote text — so the first real endnote is id `1`. Iterating skips the separators, so what you get is the endnotes a reader would count.
A document can carry both kinds at once — they are independent sequences, numbered separately, and Word renders footnotes in Arabic numerals and endnotes in lower-case Roman by default.
replace_text() reaches endnotes under `footnotes=True`, which covers both note stories:
```
document.replace_text("Babbage", "Charles Babbage", footnotes=True)
```
# Form fields and content controls
Word has two mechanisms for "a place in the document where someone fills something in", from two different eras. Both are read here; which one a document uses depends on which Word feature built it.
- **Legacy form fields** (`w:fldChar` with `w:ffData`) — the text input, check box and drop-down from the old Forms toolbar. Values are read *and written*.
- **Content controls** (`w:sdt`, "structured document tags") — the modern replacement, and also what a mail-merge or template tool typically inserts. Read-only here.
## Legacy form fields
Document.form_fields returns every one in the body, and Paragraph.form_fields those of a single paragraph:
```
from docx import Document
document = Document("form.docx")
for field in document.form_fields:
print(field.name, field.type, field.value)
```
```
Surname WD_FORM_FIELD_TYPE.TEXT 'Lovelace'
Agreed WD_FORM_FIELD_TYPE.CHECK_BOX True
Department WD_FORM_FIELD_TYPE.DROP_DOWN 'Engineering'
```
FormField.value reads and writes:
```
field.value = "Babbage" # a text input
field.value = True # a check box
field.value = "Finance" # a drop-down, by entry text
```
What value means follows the field type:
| Type | Value |
| ----------- | ------------------------------------------------- |
| `TEXT` | the result text Word last rendered |
| `CHECK_BOX` | a `bool` |
| `DROP_DOWN` | the selected entry, `""` when nothing is selected |
Warning
Word renders an empty text field as filler — five spaces or similar — and that filler is what `value` returns, because it is what the document actually contains. Test against default rather than against `""` when you need to know whether a field was filled in.
The other properties describe how Word presents the field:
```
field.name # the bookmark name Word gives it
field.default
field.enabled
field.help_text
field.status_text
field.max_length # text inputs
field.text_type # WD_TEXT_FORM_FIELD_TYPE: REGULAR_TEXT, NUMBER_TEXT, DATE_TEXT, ...
field.items # drop-down entries
field.calc_on_exit
```
## Content controls
A `w:sdt` wraps content rather than standing in for it, which has one consequence worth knowing: **text inside a content control is ordinary document text**. Before 2.0.0 it was invisible to the API — a document built from a template could read back as empty.
Document.content_controls and Paragraph.content_controls give you the controls themselves:
```
for control in document.content_controls:
print(control.tag, control.alias, repr(control.text))
```
- tag — the machine-readable name, which is what a template tool keys on
- alias — the title Word shows the user
- type — rich text, plain text, date picker, and so on
- text — everything inside, paragraphs separated by newlines, as for a table cell
- showing_placeholder — whether what you are reading is the grey prompt text rather than a real value
- is_block_level — whether it wraps whole paragraphs and tables, or sits inline within a paragraph
Reach the content through runs, paragraphs, tables or iter_inner_content():
```
control = document.content_controls[0]
for run in control.runs:
run.bold = True
```
To change the text in a control, edit those runs, or use replace_text(), which reaches inside content controls like any other text. See [Finding and replacing text](https://toxicphreak.github.io/python-docx-ng/user/search-replace/index.md).
# Working with Headers and Footers
Word supports *page headers* and *page footers*. A page header is text that appears in the top margin area of each page, separated from the main body of text, and usually conveying context information, such as the document title, author, creation date, or the page number. The page headers in a document are the same from page to page, with only small differences in content, such as a changing section title or page number. A page header is also known as a *running head*.
A *page footer* is analogous in every way to a page header except that it appears at the bottom of a page. It should not be confused with a footnote, which is not uniform between pages. For brevity's sake, the term *header* is often used here to refer to what may be either a header or footer object, trusting the reader to understand its applicability to both object types.
## Accessing the header for a section
Headers and footers are linked to a *section*; this allows each section to have a distinct header and/or footer. For example, a landscape section might have a wider header than a portrait section.
Each section object has a `.header` property providing access to a \_Header object for that section:
```
>>> document = Document()
>>> section = document.sections[0]
>>> header = section.header
>>> header
```
A \_Header object is *always* present on `Section.header`, even when no header is defined for that section. The presence of an actual header definition is indicated by `_Header.is_linked_to_previous`:
```
>>> header.is_linked_to_previous
True
```
A value of `True` indicates the \_Header object contains no header definition and the section will display the same header as the previous section. This "inheritance" behavior is recursive, such that a "linked" header actually gets its definition from the first prior section having a header definition. This "linked" state is indicated as *"Same as previous"* in the Word UI.
A new document does not have a header (on the single section it contains) and so `.is_linked_to_previous` is `True` in that case. Note this case may be a bit counterintuitive in that there *is no previous section header* to link to. In this "no previous header" case, no header is displayed.
## Adding a header (simple case)
A header can be added to a new document simply by editing the content of the \_Header object. A \_Header object is a "story" container and its content is edited just like a Document object. Note that like a new document, a new header already contains a single (empty) paragraph:
```
>>> paragraph = header.paragraphs[0]
>>> paragraph.text = "Title of my document"
```
Note also that the act of adding content (or even just accessing `header.paragraphs`) added a header definition and changed the state of `.is_linked_to_previous`:
```
>>> header.is_linked_to_previous
False
```
## Adding "zoned" header content
A header with multiple "zones" is often accomplished using carefully placed tab stops.
The required tab-stops for a center and right-aligned "zone" are part of the `Header` and `Footer` styles in Word. If you're using a custom template rather than the *python-docx* default, it probably makes sense to define that style in your template.
Inserted tab characters (`"\t"`) are used to separate left, center, and right-aligned header content:
```
>>> paragraph = header.paragraphs[0]
>>> paragraph.text = "Left Text\tCenter Text\tRight Text"
>>> paragraph.style = document.styles["Header"]
```
The `Header` style is automatically applied to a new header, so the third line just above (applying the `Header` style) is unnecessary in this case, but included here to illustrate the general case.
## Removing a header
An unwanted header can be removed by assigning `True` to its `.is_linked_to_previous` attribute:
```
>>> header.is_linked_to_previous = True
>>> header.is_linked_to_previous
True
```
The content for a header is irreversably deleted when `True` is assigned to `.is_linked_to_previous`.
## Understanding headers in a multi-section document
The "just start editing" approach works fine for the simple case, but to make sense of header behaviors in a multi-section document, a few simple concepts will be helpful. Here they are in a nutshell:
1. Each section can have its own header definition (but doesn't have to).
1. A section that lacks a header definition inherits the header of the section before it. The `_Header.is_linked_to_previous` property simply reflects the presence of a header definition, `False` when a definition is present and `True` when not.
1. Lacking a header definition is the default state. A new document has no defined header and neither does a newly-inserted section. `.is_linked_to_previous` reports `True` in both those cases.
1. The content of a `_Header` object is its own content if it has a header definition. If not, its content is that of the first prior section that *does* have a header definition. If no sections have a header definition, a new one is added on the first section and all other sections inherit that one. This adding of a header definition happens the first time header content is accessed, perhaps by referencing `header.paragraphs`.
## Adding a header definition (general case)
An explicit header definition can be given to a section that lacks one by assigning `False` to its `.is_linked_to_previous` property:
```
>>> header.is_linked_to_previous
True
>>> header.is_linked_to_previous = False
>>> header.is_linked_to_previous
False
```
The newly added header definition contains a single empty paragraph. Note that leaving the header this way is occasionally useful as it effectively "turns-off" a header for that section and those after it until the next section with a defined header.
Assigning `False` to `.is_linked_to_previous` on a header that already has a header definition does nothing.
### Inherited content is automatically located
Editing the content of a header edits the content of the *source* header, taking into account any "inheritance". So for example, if the section 2 header inherits from section 1 and you edit the section 2 header, you actually change the contents of the section 1 header. A new header definition is not added for section 2 unless you first explicitly assign `False` to its `.is_linked_to_previous` property.
# Images
## Inline pictures
An inline picture sits in the text flow like a very large character:
```
from docx import Document
from docx.shared import Inches
document = Document()
document.add_picture("logo.png", width=Inches(1.25))
```
Document.add_picture() puts the image in a paragraph of its own. To place one within a paragraph you are building, use Run.add_picture():
```
paragraph = document.add_paragraph("As shown here: ")
paragraph.add_run().add_picture("figure.png", width=Inches(2))
```
Giving only `width` or only `height` scales the other to match, preserving the aspect ratio. Giving neither uses the image's native size, from its own DPI.
## Floating pictures
A floating picture is anchored to something on the page and text wraps around it — a logo in a corner, a figure beside a paragraph:
```
from docx.enum.shape import WD_WRAP_TYPE
from docx.shared import Cm
run = document.add_paragraph("Text that flows around the figure. ").add_run()
shape = run.add_float_picture(
"figure.png",
width=Cm(4),
left=Cm(1),
top=Cm(0.5),
wrap_type=WD_WRAP_TYPE.SQUARE,
)
```
`left` and `top` are offsets from whatever the image is positioned against, which `relative_from_h` and `relative_from_v` choose — the column and the paragraph by default, or the page, the margin, a specific margin, or the character position.
WD_WRAP_TYPE controls how text behaves around it:
| Value | Effect |
| ------------ | ----------------------------------------------------------- |
| `SQUARE` | text keeps clear of the image's bounding box |
| `TIGHT` | text follows the image's outline |
| `THROUGH` | text also fills enclosed transparent areas |
| `TOP_BOTTOM` | text breaks above and below, none beside |
| `NONE` | text ignores the image, which then overlaps or underlies it |
To put an image *behind* the text, both settings are needed — `behind_text` takes effect only when nothing is keeping text out of the way in the first place:
```
shape.wrap_type = WD_WRAP_TYPE.NONE
shape.behind_text = True
```
For the specific case of a background image on every page, see [Watermarks](https://toxicphreak.github.io/python-docx-ng/user/watermarks/index.md), which handles the header placement for you.
Document.floating_shapes lists the ones already in a document, and each shape exposes `width`, `height`, `left`, `top`, `horizontal_align`, `vertical_align`, `z_order`, `allow_overlap` and `set_wrap_distance()`.
## Alternative text
Screen readers announce the description; the title is presented separately and generally not announced, so accessibility depends on the description:
```
picture = document.add_picture("chart.png", description="Revenue by quarter, 2026")
picture.title = "Revenue chart"
```
Both are read/write on InlineShape and FloatingShape, and both add_picture() and add_float_picture() accept them as arguments. Assigning `None` removes them:
```
for shape in document.inline_shapes:
if not shape.description:
print("missing alt text")
```
## Supported formats
PNG, JPEG, GIF, BMP, TIFF, WebP, SVG, EMF and WMF.
SVG needs care, because Word will not render one without a raster fallback — it stores both and shows the fallback in any version that cannot draw the vector:
```
document.add_picture("diagram.svg", svg_fallback="diagram.png")
```
Without `svg_fallback` the image is embedded but may not display.
The format is detected from the file's own header rather than its extension, so a mislabelled file still works. An unrecognised one raises UnrecognizedImageError.
## Photos from a phone: EXIF orientation
A camera does not rotate the pixels when you turn it sideways. It stores the sensor's landscape frame and writes an EXIF `Orientation` tag saying which way up it goes. A portrait photo off a phone is therefore a *landscape* JPEG with a tag on it, and inserting it naively puts it in the document on its side — and, if you gave only a width, at the wrong aspect ratio too, because the height was derived from the stored dimensions.
add_picture() and add_float_picture() honour the tag:
```
shape = document.add_picture("photo-from-phone.jpg", width=Inches(2))
shape.width # -> Inches(2)
shape.height # -> Inches(4), from the displayed 2:4 shape, not the stored 4:2
```
The rotation goes into the DrawingML — `a:xfrm/@rot`, in sixtieths of a degree — and never into the pixels. The image part stays byte-identical to the file on disk, so the sha1 deduplication keeps working and the same photo inserted twice is still one part.
Pass `honor_exif_orientation=False` to insert the stored frame unrotated.
Image reports both sets of dimensions, and the distinction is the whole point:
```
from docx.image.image import Image
image = Image.from_file("photo-from-phone.jpg")
image.orientation # -> 6, "rotate 90° clockwise to display"
image.is_rotated # -> True, this orientation exchanges width and height
image.px_width # -> 4032, the frame as stored
image.px_display_width # -> 3024, the frame as a viewer shows it
image.width # -> the stored width as a Length
image.display_width # -> the displayed width as a Length
```
`px_width` and `px_height` keep meaning what they always meant — the stored dimensions — so nothing that read them changed behaviour. The `display_*` accessors are the new ones. scaled_dimensions() scales from the display aspect ratio by default and takes the same `honor_exif_orientation=False`.
An image whose format cannot carry the tag, or which does not set it, reports `orientation == 1`, for which everything above is a no-op.
## Reading the images already in a document
The counterpart of `add_picture()`. Each shape hands back the image it displays:
```
for shape in document.inline_shapes:
image = shape.image
if image is None:
continue # -- a chart, a diagram, a linked picture
print(image.filename, image.content_type, image.px_width, image.px_height)
Path(image.filename).write_bytes(image.blob)
```
InlineShape.image and FloatingShape.image are `None` rather than an error when there is no image to give — a chart, a SmartArt diagram, a picture linked to a file on disk rather than embedded, or a relationship the document does not resolve.
For an SVG picture, `.image` is the raster fallback Word displays and .svg_image the vector source:
```
shape.image.content_type # -> "image/png", the fallback
shape.svg_image.content_type # -> "image/svg+xml", the original
```
Document.images is the package-level view — the distinct images the body embeds, deduplicated, with no shape needed to reach them:
```
for image in document.images:
print(image.filename, len(image.blob))
```
# Installing
```
pip install python-docx-ng
```
The import name is `docx`
The distribution is `python-docx-ng`, the importable package is `docx`:
```
import docx
from docx import Document
```
That is deliberate — it makes this a drop-in replacement for `python-docx`, so existing code and every upstream example keep working. It also means **`python-docx-ng` and `python-docx` cannot be installed side by side**: they claim the same import name, and whichever was installed last wins. Uninstall one before installing the other.
```
pip uninstall python-docx
pip install python-docx-ng
```
## Requirements
| | |
| ---------------------------------------------------------------- | ---------- |
| Python | 3.9 – 3.14 |
| [lxml](https://pypi.org/project/lxml/) | >= 6.1.0 |
| [typing_extensions](https://pypi.org/project/typing-extensions/) | >= 4.9.0 |
Both dependencies are installed for you. There are no others — no test framework, no documentation tooling.
lxml is floored at 6.1.0 because that is the first release fixing CVE-2026-41066, an XXE-to-local-files hole in the default configuration of `iterparse()` and `ETCompatXMLParser()`; every 4.x and 5.x release is affected. This library uses neither API and sets `resolve_entities=False` on its own parser, so it was never exposed itself, but it will not pull a known-vulnerable XML parser into your dependency tree.
If you are pinned below lxml 6
Another package in your environment may cap lxml below 6. In that case this release will not resolve, and there is no supported workaround — the floor is a security boundary rather than a compatibility one.
## Other installers
```
uv add python-docx-ng
poetry add python-docx-ng
pipenv install python-docx-ng
```
## Upgrading from 0.9.x
2.0.0 rebases onto upstream python-docx v1.2.0 and **contains breaking changes**. Read the [migration guide](https://toxicphreak.github.io/python-docx-ng/user/migrating-from-0-9/index.md) before upgrading.
The 0.9.x releases are yanked on PyPI. They require `lxml<5`, which has no wheels for Python 3.13 or later, and they declare a test framework as a runtime dependency. An exact pin such as `python-docx-ng==0.9.7` still resolves, so existing locked builds are unaffected, but nothing new will select one.
## Installing from source
```
git clone https://github.com/toxicphreAK/python-docx-ng.git
cd python-docx-ng
uv sync
```
[uv](https://docs.astral.sh/uv/) is what this project uses; `uv sync` creates the environment and installs the development dependencies. See [CONTRIBUTING.md](https://github.com/toxicphreAK/python-docx-ng/blob/main/CONTRIBUTING.md) for running the tests.
# Migrating from 0.9.x
Version 2.0.0 restarts *python-docx-ng* from upstream python-docx v1.2.0.
The 0.9.x line had diverged from upstream v0.8.11 in 2021 and drifted for four years. Rather than merge four years of upstream change into that tree, 2.0.0 branches from upstream and re-applies the python-docx-ng features on top, one at a time. The benefit is a typed, tested core that tracks upstream; the cost is that several 0.9.x additions are gone, replaced by upstream implementations of the same features that are shaped differently.
**This is a breaking release.** Read this page before upgrading. Code that only used the parts of the API that upstream python-docx also has is very likely unaffected.
## Features upstream has since implemented
These existed in 0.9.x as fork additions. Upstream has since implemented the same features, and 2.0.0 uses upstream's version.
### Comments
0.9.x added comments to a paragraph. Upstream models them as a collection on the document, with the comment range marked on runs.
```
# -- 0.9.x --
paragraph.add_comment("Needs a citation.", author="RS", initials="rs")
paragraph.comments
# -- 2.0.0 --
comment = document.add_comment(paragraph.runs, "Needs a citation.", author="RS", initials="rs")
document.comments # -- the Comments collection --
document.comments.get(comment_id)
run.mark_comment_range(last_run, comment_id) # -- for finer control --
```
The `docx.text.comment` module is gone; the objects live in `docx.comments`. A comment is now a block-item container, so it can hold several paragraphs and even tables, and `comment.text` joins its paragraphs with newlines.
See [comments](https://toxicphreak.github.io/python-docx-ng/user/comments/index.md) for the full API.
### Hyperlinks
0.9.x added its own hyperlink handling. Upstream's Hyperlink reads links already in a document and exposes `.address`, `.fragment`, `.runs`, `.text` and `.contains_page_break`; Paragraph.hyperlinks lists them, and `paragraph.text` includes hyperlink text, which it did not in 0.8.11.
Paragraph.add_hyperlink is still present — that is a python-docx-ng addition upstream has not adopted — but its signature has changed:
```
# -- 2.0.0 --
paragraph.add_hyperlink("python-docx-ng", address="https://example.com/")
paragraph.add_hyperlink("see above", fragment="my_bookmark") # -- internal link --
```
It returns a Hyperlink, not a run.
### Table cell access
This is the change most likely to affect existing code.
In 0.9.x, `Table._cells` built a row-major matrix of the layout grid, and `Table.cell()`, `Table.row_cells()` and `Table.column_cells()` indexed into it. Upstream returns a flat list, and — importantly — `_Row.cells` returns only the cells that are actually present.
Word allows a row to start late or end early, so rows of the same table can have different numbers of cells. Two new properties report the gap:
```
for row in table.rows:
leading = row.grid_cols_before # -- unpopulated grid columns before the first cell --
trailing = row.grid_cols_after # -- and after the last --
for cell in row.cells:
...
```
If you were relying on every row having one cell per column, add `grid_cols_before` and `grid_cols_after` to your indexing, or use Table.cell, which addresses the layout grid directly and raises `IndexError` for a grid position the row does not occupy.
`Table.cell()` now locates its target without materializing the whole grid, so reading a table cell-by-cell costs time proportional to the number of cells rather than to its square. `Table.row_cells()` is deprecated in favour of `table.rows[i].cells`.
## Behaviour changes
### `Paragraph.text` with tracked changes
In 0.9.x — and in upstream — revision markup was not modelled at all. Runs inside a `w:ins` were skipped, so inserted text was missing, and deleted text was missing too because it lives in `w:delText` rather than `w:t`. The result was neither the original nor the final version of the document but a third thing matching no view Word offers, and it was wrong silently.
`Paragraph.text` is now the document **as it now reads** — every revision accepted:
```
paragraph.text # -- insertions in, deletions out --
paragraph.original_text # -- deletions in, insertions out --
```
`Paragraph.runs` follows the same reading, so a run inside a `w:ins` now appears in it and a run inside a `w:del` does not.
If you were relying on the old behaviour to strip insertions, use `original_text`. If you want the revision markup gone from the file altogether, call `document.accept_all_revisions()` or `document.reject_all_revisions()`. See Document.revisions for reading the individual changes.
### `Paragraph.text` with simple fields
The cached result of a `w:fldSimple` — the page number a `PAGE` field displays, the text a cross-reference resolves to — is now part of `Paragraph.text`. It was previously skipped, so such text went missing. This is displayed text and belongs there; a field *instruction* (`w:instrText`) is still never reported as text, because it is not.
### `Font.highlight_color`
In 0.9.x this fell back to the `w:shd` shading fill when no highlight was set, and accepted RGB values on assignment. Highlighting and shading are different things in Word, and conflating them meant you could not tell them apart. They are now separate:
```
font.highlight_color # -- a WD_COLOR_INDEX member or None, never an RGB value --
font.shading_fill # -- an RGBColor, the string "auto", or None --
```
`highlight_color` also distinguishes `WD_COLOR_INDEX.NO_HIGHLIGHT` — highlighting explicitly turned off, which Word writes as `w:highlight w:val="none"` — from `None`, which means no setting is present and the value is inherited.
### `ParagraphFormat.outline_level`
0.9.x returned `9` when no outline level was set. `None` and `9` are different things: `None` means the level is inherited from the style hierarchy, and `9` is Word's explicit "Body Text" level, which deliberately excludes the paragraph from the outline. 2.0.0 reports them distinctly, and assigning a value outside 0–9 raises `ValueError` rather than writing an invalid document.
### `Section.orientation`
Assigning an orientation now exchanges `page_width` and `page_height` too, so the page is actually rotated. Previously it set `w:pgSz/@w:orient` alone and left the dimensions where they were, giving a section declared landscape at portrait dimensions — which Word renders as portrait.
**Remove the workaround if you have one.** Nearly everyone who hit this wrote something like:
```
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width # -- delete this --
```
The two swaps now cancel out and the page comes back the size it started, which is the same broken result as before, arrived at from the other direction. Delete the second line:
```
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height # -- (11in, 8.5in) --
```
Setting the orientation a section already has does nothing, so the assignment is safe to repeat. For a page size that is not simply the rotation of the current one, set `page_width` and `page_height` explicitly afterwards. Margins are not moved.
## Removed without replacement
`Section.paragraphs`\
Use Section.iter_inner_content, which generates the paragraphs and tables of a section in document order rather than paragraphs alone.
`Table.section`\
Removed. The 0.9.x implementation walked to the first following `w:sectPr`, which returns the wrong section for every table but those in the last section of a multi-section document. There is no correct one-line replacement; iterate Document.sections and Section.iter_inner_content to find the section a table belongs to.
## Fork features, re-applied and reshaped
These are python-docx-ng features that upstream does not have. They are back on the 2.0.0 base, in some cases with a different API than 0.9.x had.
### Table and cell borders
0.9.x exposed borders as a sequence with `add_border()` and `remove_border()` methods, a `_Border.name` setter that returned a value Python discarded, and line styles as bare strings validated against a 200-entry tuple. 2.0.0 makes it a mapping keyed by edge:
```
# -- 0.9.x --
table.borders.add_border("top", "single", sz=8, color="FF0000")
# -- 2.0.0 --
from docx.enum.table import WD_LINE_STYLE
from docx.shared import Pt, RGBColor
table.borders["top"].line = WD_LINE_STYLE.SINGLE
table.borders["top"].size = Pt(1)
table.borders["top"].color = RGBColor(0xFF, 0x00, 0x00)
table.borders["top"].line = None # -- remove the edge --
```
Edges are named for the XML: `top`, `start`, `left`, `bottom`, `end`, `right`, `insideH` and `insideV`, plus `tl2br` and `tr2bl` on a cell. Sizes are Length values, so `Pt(1)` composes as it does everywhere else, rather than a raw count of eighths of a point.
### Footnotes
0.9.x had `paragraph.add_footnote(text)`, derived from bayoo-docx, and a `paragraph.footnotes` that returned a bool. The 2.0.0 implementation mirrors the shape upstream gave comments:
```
footnote = document.footnotes.add_footnote("See Smith (2019), p. 42.")
paragraph.add_run().add_footnote_reference(footnote)
len(document.footnotes)
document.footnotes.get(footnote.footnote_id)
```
Creating a footnote and referencing it are separate steps, because a footnote can be referenced from anywhere and one that is never referenced does not render. A footnote is a block-item container, so it can hold several paragraphs, tables and images.
Word keeps two structural footnotes at ids -1 and 0 for the separator rules. Those are in the part but never in the collection, so authored footnotes are numbered from 1.
See [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/footnotes/index.md).
### Form fields
0.9.x exposed only element classes; there was no proxy API and no way to read or set a field value without dropping to the XML. 2.0.0 adds one:
```
values = {f.name: f.value for f in document.form_fields}
field = document.form_fields[0]
field.value = "Carol Chen" # -- str, or bool for a check box --
field.items # -- the entries of a drop-down --
```
`.value` reads and writes in the form natural to the kind of field. See FormField.
### AltChunk
0.9.x had an `AltchunkPart` but no public API. 2.0.0 adds one:
```
document.add_alt_chunk(b"
Imported.
", "text/html")
document.alt_chunks
```
Word performs the import when it opens the document, so the embedded content stays invisible to this library until then.
## Still to come
Some 0.9.x features have not been re-applied yet. Check the [2.0.0 milestone](https://github.com/toxicphreAK/python-docx-ng/milestone/1) before assuming something is gone for good.
# Numbered and bulleted lists
A list number is nowhere in the document body. Word stores which list a paragraph belongs to and computes "1.", "a)" or "iii." from `numbering.xml` at display time, which is why reading a document gives you paragraph text with the numbers missing.
This library computes them the same way Word does.
## Reading the numbers
Document.list_numbers pairs every list paragraph in the body with the number a reader sees:
```
from docx import Document
document = Document("report.docx")
for paragraph, number in document.list_numbers:
print(number, paragraph.text)
```
```
1. First point
2. Second point
a) A sub-point
3. Third point
```
Paragraphs inside tables are included, because they count towards the same lists.
Paragraph.list_number gives the number for a single paragraph, and `None` when it is not in a list:
```
paragraph.list_number # -> "2." or None
```
Warning
A list number depends on every paragraph before it, so reading Paragraph.list_number in a loop is quadratic. Use Document.list_numbers, which walks the document once.
## Which list a paragraph is in
Paragraph.numbering returns a ParagraphNumbering, or `None` when the paragraph is not in a list:
```
item = document.add_paragraph("first item", style="List Number")
numbering = item.numbering
numbering.num_id # -> the list this paragraph belongs to
numbering.level # -> indent level, 0 for the outermost
numbering.from_style # -> True when the style applies it, not the paragraph
numbering.level_definition # -> the NumberingLevel, for format and start value
```
The level definition is where the appearance lives:
```
level = item.numbering.level_definition
level.number_format # -> WD_NUMBER_FORMAT.DECIMAL
level.start # -> 1
level.is_bullet # -> False
level.format_number(3) # -> "3."
```
format_number() applies the level's own template, so a level written as `%1)` in lower letters gives `"c)"` for the same input. WD_NUMBER_FORMAT lists the formats.
## Creating a list
The simplest lists are the built-in styles, which carry their own numbering:
```
document.add_paragraph("first item", style="List Bullet")
document.add_paragraph("first item", style="List Number")
```
To put further paragraphs in the *same* list as an existing one, take its `num_id`:
```
first = document.add_paragraph("one", style="List Number")
second = document.add_paragraph("two")
second.set_numbering(first.numbering.num_id)
```
set_numbering() also takes a level, so nesting is a matter of saying which:
```
sub = document.add_paragraph("one, continued")
sub.set_numbering(first.numbering.num_id, level=1)
```
Numbering applied directly like this overrides whatever the paragraph's style would apply. remove_numbering() takes it off again.
## Restarting numbering
```
item.restart_numbering() # begins again at 1
item.restart_numbering(10) # begins again at 10
```
This paragraph and every later one in the same list start a new sequence; paragraphs before it keep the original one. That is what Word's own "Restart at 1" does.
It is worth knowing what happens underneath, because it explains the return value. OOXML has no counter to reset. A second `w:num` is created against the same abstract definition, carrying a `w:startOverride`, and the affected paragraphs are pointed at it — so restart_numbering() returns the `num_id` of that new list:
```
new_num_id = item.restart_numbering()
later = document.add_paragraph("still in the restarted list")
later.set_numbering(new_num_id)
```
It raises `ValueError` when the paragraph is not in a list.
## The numbering part
Document.numbering is the whole of `numbering.xml`, for finding a list to join or inspecting one:
```
definition = document.numbering.get(item.numbering.num_id)
definition.num_id
definition.abstract_num_id
definition.levels # -> the NumberingLevel objects, one per level
definition.level(0)
```
Numbering.restart() creates the overriding definition directly, when you want the new list without repointing any paragraphs:
```
restarted = document.numbering.restart(item.numbering.num_id, ilvl=0, start=1)
```
## Defining a list from scratch
Everything above joins or restarts a list the numbering part already defines. When the format you want is not in the template — Roman numerals at the top level, a bullet character of your own, a particular indent step — define one:
```
definition = document.numbering.add_numbered_definition()
document.add_paragraph("one").set_numbering(definition.num_id)
```
add_numbered_definition() and add_bulleted_definition() are the two common cases. Both take a `depth` (nine levels by default, which is what Word writes) and an `indent_step`:
```
from docx.enum.numbering import WD_NUMBER_FORMAT
from docx.shared import Inches
legal = document.numbering.add_numbered_definition(
depth=3,
formats=[
WD_NUMBER_FORMAT.UPPER_ROMAN,
WD_NUMBER_FORMAT.UPPER_LETTER,
WD_NUMBER_FORMAT.DECIMAL,
],
indent_step=Inches(0.3),
)
bullets = document.numbering.add_bulleted_definition(bullets=["—", "·"])
```
add_definition() is the general form, taking a level specification directly. Both shorthands are built on it:
```
definition = document.numbering.add_definition([
{"number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%1.", "start": 1},
{"number_format": WD_NUMBER_FORMAT.LOWER_LETTER, "level_text": "%2)"},
])
```
In a `level_text`, `%1` interpolates the counter of level 0, `%2` that of level 1, and so on — `"%1.%2."` is what produces "2.3.". A definition has at most nine levels; more raises `ValueError`.
Each returns a NumberingDefinition, whose `num_id` is what set_numbering() takes.
Note
`w:nsid` and `w:tmpl` are deliberately not written. They are the identifiers Word uses to recognise a definition as one of its own and to match it against a gallery entry; inventing values would make Word treat unrelated lists as the same list, and they are optional.
## Changing a level
NumberingLevel.set() changes an existing level in place, and returns the level so calls chain:
```
definition.level(0).set(
number_format=WD_NUMBER_FORMAT.UPPER_ROMAN,
level_text="%1.",
suffix="tab",
alignment="left",
indent=Inches(0.5),
hanging_indent=Inches(0.25),
start=1,
)
```
`suffix` is what follows the number — `"tab"`, `"space"` or `"nothing"` — and `restart_after_level` is what makes a sub-list start over when the level above it advances. Every argument is keyword-only and optional; the ones you leave out are left alone.
# Quickstart
Getting started with `python-docx-ng` is easy. Let's walk through the basics.
## Opening a document
First thing you'll need is a document to work on. The easiest way is this:
```
from docx import Document
document = Document()
```
This opens up a blank document based on the default "template", pretty much what you get when you start a new document in Word using the built-in defaults. You can open and work on an existing Word document using `python-docx`, but we'll keep things simple for the moment.
## Adding a paragraph
Paragraphs are fundamental in Word. They're used for body text, but also for headings and list items like bullets.
Here's the simplest way to add one:
```
paragraph = document.add_paragraph('Lorem ipsum dolor sit amet.')
```
This method returns a reference to a paragraph, newly added paragraph at the end of the document. The new paragraph reference is assigned to `paragraph` in this case, but I'll be leaving that out in the following examples unless I have a need for it. In your code, often times you won't be doing anything with the item after you've added it, so there's not a lot of sense in keep a reference to it hanging around.
It's also possible to use one paragraph as a "cursor" and insert a new paragraph directly above it:
```
prior_paragraph = paragraph.insert_paragraph_before('Lorem ipsum')
```
This allows a paragraph to be inserted in the middle of a document, something that's often important when modifying an existing document rather than generating one from scratch.
## Adding a heading
In anything but the shortest document, body text is divided into sections, each of which starts with a heading. Here's how to add one:
```
document.add_heading('The REAL meaning of the universe')
```
By default, this adds a top-level heading, what appears in Word as 'Heading 1'. When you want a heading for a sub-section, just specify the level you want as an integer between 1 and 9:
```
document.add_heading('The role of dolphins', level=2)
```
If you specify a level of 0, a "Title" paragraph is added. This can be handy to start a relatively short document that doesn't have a separate title page.
## Adding a page break
Every once in a while you want the text that comes next to go on a separate page, even if the one you're on isn't full. A "hard" page break gets this done:
```
document.add_page_break()
```
If you find yourself using this very often, it's probably a sign you could benefit by better understanding paragraph styles. One paragraph style property you can set is to break a page immediately before each paragraph having that style. So you might set your headings of a certain level to always start a new page. More on styles later. They turn out to be critically important for really getting the most out of Word.
## Adding a table
One frequently encounters content that lends itself to tabular presentation, lined up in neat rows and columns. Word does a pretty good job at this. Here's how to add a table:
```
table = document.add_table(rows=2, cols=2)
```
Tables have several properties and methods you'll need in order to populate them. Accessing individual cells is probably a good place to start. As a baseline, you can always access a cell by its row and column indicies:
```
cell = table.cell(0, 1)
```
This gives you the right-hand cell in the top row of the table we just created. Note that row and column indicies are zero-based, just like in list access.
Once you have a cell, you can put something in it:
```
cell.text = 'parrot, possibly dead'
```
Frequently it's easier to access a row of cells at a time, for example when populating a table of variable length from a data source. The `.rows` property of a table provides access to individual rows, each of which has a `.cells` property. The `.cells` property on both `Row` and `Column` supports indexed access, like a list:
```
row = table.rows[1]
row.cells[0].text = 'Foo bar to you.'
row.cells[1].text = 'And a hearty foo bar to you too sir!'
```
The `.rows` and `.columns` collections on a table are iterable, so you can use them directly in a `for` loop. Same with the `.cells` sequences on a row or column:
```
for row in table.rows:
for cell in row.cells:
print(cell.text)
```
If you want a count of the rows or columns in the table, just use `len()` on the sequence:
```
row_count = len(table.rows)
col_count = len(table.columns)
```
You can also add rows to a table incrementally like so:
```
row = table.add_row()
```
This can be very handy for the variable length table scenario we mentioned above:
```
# get table data -------------
items = (
(7, '1024', 'Plush kittens'),
(3, '2042', 'Furbees'),
(1, '1288', 'French Poodle Collars, Deluxe'),
)
# add table ------------------
table = document.add_table(1, 3)
# populate header row --------
heading_cells = table.rows[0].cells
heading_cells[0].text = 'Qty'
heading_cells[1].text = 'SKU'
heading_cells[2].text = 'Description'
# add a data row for each item
for item in items:
cells = table.add_row().cells
cells[0].text = str(item.qty)
cells[1].text = item.sku
cells[2].text = item.desc
```
The same works for columns, although I've yet to see a use case for it.
Word has a set of pre-formatted table styles you can pick from its table style gallery. You can apply one of those to the table like this:
```
table.style = 'LightShading-Accent1'
```
The style name is formed by removing all the spaces from the table style name. You can find the table style name by hovering your mouse over its thumbnail in Word's table style gallery.
## Adding a picture
Word lets you place an image in a document using the `Insert > Photo > Picture from file...` menu item. Here's how to do it in `python-docx`:
```
document.add_picture('image-filename.png')
```
This example uses a path, which loads the image file from the local filesystem. You can also use a *file-like object*, essentially any object that acts like an open file. This might be handy if you're retrieving your image from a database or over a network and don't want to get the filesystem involved.
### Image size
By default, the added image appears at *native* size. This is often bigger than you want. Native size is calculated as `pixels / dpi`. So a 300x300 pixel image having 300 dpi resolution appears in a one inch square. The problem is most images don't contain a dpi property and it defaults to 72 dpi. This would make the same image appear 4.167 inches on a side, somewhere around half the page.
To get the image the size you want, you can specify either its width or height in convenient units, like inches or centimeters:
```
from docx.shared import Inches
document.add_picture('image-filename.png', width=Inches(1.0))
```
You're free to specify both width and height, but usually you wouldn't want to. If you specify only one, `python-docx` uses it to calculate the properly scaled value of the other. This way the *aspect ratio* is preserved and your picture doesn't look stretched.
The `Inches` and `Cm` classes are provided to let you specify measurements in handy units. Internally, `python-docx` uses English Metric Units, 914400 to the inch. So if you forget and just put something like `width=2` you'll get an extremely small image :). You'll need to import them from the `docx.shared` sub-package. You can use them in arithmetic just like they were an integer, which in fact they are. So an expression like `width = Inches(3) / thing_count` works just fine.
## Applying a paragraph style
If you don't know what a Word paragraph style is you should definitely check it out. Basically it allows you to apply a whole set of formatting options to a paragraph at once. It's a lot like CSS styles if you know what those are.
You can apply a paragraph style right when you create a paragraph:
```
document.add_paragraph('Lorem ipsum dolor sit amet.', style='ListBullet')
```
This particular style causes the paragraph to appear as a bullet, a very handy thing. You can also apply a style afterward. These two lines are equivalent to the one above:
```
paragraph = document.add_paragraph('Lorem ipsum dolor sit amet.')
paragraph.style = 'List Bullet'
```
The style is specified using its style name, 'List Bullet' in this example. Generally, the style name is exactly as it appears in the Word user interface (UI).
## Applying bold and italic
In order to understand how bold and italic work, you need to understand a little about what goes on inside a paragraph. The short version is this:
1. A paragraph holds all the *block-level* formatting, like indentation, line height, tabs, and so forth.
1. Character-level formatting, such as bold and italic, are applied at the *run* level. All content within a paragraph must be within a run, but there can be more than one. So a paragraph with a bold word in the middle would need three runs, a normal one, a bold one containing the word, and another normal one for the text after.
When you add a paragraph by providing text to the `.add_paragraph()` method, it gets put into a single run. You can add more using the `.add_run()` method on the paragraph:
```
paragraph = document.add_paragraph('Lorem ipsum ')
paragraph.add_run('dolor sit amet.')
```
This produces a paragraph that looks just like one created from a single string. It's not apparent where paragraph text is broken into runs unless you look at the XML. Note the trailing space at the end of the first string. You need to be explicit about where spaces appear at the beginning and end of a run. They're not automatically inserted between runs. Expect to be caught by that one a few times :).
Run objects have both a `.bold` and `.italic` property that allows you to set their value for a run:
```
paragraph = document.add_paragraph('Lorem ipsum ')
run = paragraph.add_run('dolor')
run.bold = True
paragraph.add_run(' sit amet.')
```
which produces text that looks like this: 'Lorem ipsum **dolor** sit amet.'
Note that you can set bold or italic right on the result of `.add_run()` if you don't need it for anything else:
```
paragraph.add_run('dolor').bold = True
# is equivalent to:
run = paragraph.add_run('dolor')
run.bold = True
# except you don't have a reference to `run` afterward
```
It's not necessary to provide text to the `.add_paragraph()` method. This can make your code simpler if you're building the paragraph up from runs anyway:
```
paragraph = document.add_paragraph()
paragraph.add_run('Lorem ipsum ')
paragraph.add_run('dolor').bold = True
paragraph.add_run(' sit amet.')
```
## Applying a character style
In addition to paragraph styles, which specify a group of paragraph-level settings, Word has *character styles* which specify a group of run-level settings. In general you can think of a character style as specifying a font, including its typeface, size, color, bold, italic, etc.
Like paragraph styles, a character style must already be defined in the document you open with the `Document()` call (*see* [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md)).
A character style can be specified when adding a new run:
```
paragraph = document.add_paragraph('Normal text, ')
paragraph.add_run('text with emphasis.', 'Emphasis')
```
You can also apply a style to a run after it is created. This code produces the same result as the lines above:
```
paragraph = document.add_paragraph('Normal text, ')
run = paragraph.add_run('text with emphasis.')
run.style = 'Emphasis'
```
As with a paragraph style, the style name is as it appears in the Word UI.
# Tracked changes
A document edited with Word's "Track Changes" on carries its history: insertions in `w:ins` elements, deletions in `w:del`, and the same treatment for formatting changes, paragraph marks and table rows. This library reads that history and can accept or reject any part of it.
## What `Paragraph.text` means here
Text in a document with tracked changes has two well-defined readings, and this library gives you both:
```
paragraph.text # the document as it now reads
paragraph.original_text # the document as it read before the changes
```
text includes insertions and excludes deletions — what you would get by accepting everything. original_text does the reverse. For a paragraph with no revisions the two are identical.
Neither is "the text with markup shown". Word displays deletions struck through *alongside* insertions, which is a rendering rather than a string.
Note
This changed in 2.0.0. The 0.9.x line dropped both insertions and deletions, so the result matched neither reading. Paragraph.runs likewise now includes runs inside a `w:ins`.
## Reading the revisions
Document.revisions returns every revision in the body, in document order:
```
from docx import Document
document = Document("reviewed.docx")
for revision in document.revisions:
print(revision.type, revision.author, revision.date, repr(revision.text))
```
```
WD_REVISION_TYPE.INSERTION Ada Lovelace 2026-03-14 09:12:00 'and therefore '
WD_REVISION_TYPE.DELETION Charles Babbage 2026-03-14 10:03:00 'possibly '
```
Each Revision carries:
- type — a WD_REVISION_TYPE member
- author and date — `date` is `None` when Word recorded none
- text — the text inserted or deleted
- is_paragraph_mark — a paragraph split or merge rather than a text change
- is_row — a table row inserted or deleted
Paragraph.revisions narrows this to one paragraph.
### Who changed what
```
authors = {revision.author for revision in document.revisions}
```
## Accepting and rejecting
Individually:
```
for revision in document.revisions:
if revision.author == "Ada Lovelace":
revision.accept()
else:
revision.reject()
```
Accepting an insertion keeps the text and drops the marker; rejecting it removes the text. For a deletion it is the other way round.
Wholesale, on the document or on one paragraph — each returns how many revisions it handled:
```
document.accept_all_revisions() # -> 12
document.reject_all_revisions()
paragraph.accept_all_revisions()
paragraph.reject_all_revisions()
```
Warning
Accepting or rejecting mutates the tree, so a list of revisions taken beforehand goes stale. Re-read Document.revisions rather than holding onto revisions across a change, and iterate over a snapshot — as in the loop above, which reads the list once — rather than over a live traversal.
## Turning tracking on
Settings.track_revisions is the document-wide flag Word's "Track Changes" button sets:
```
document.settings.track_revisions = True
document.save("for-review.docx")
```
This asks Word to record changes made from now on. It does not cause edits made through this library to be recorded as revisions — those are written directly, as if tracking were off.
# Finding, replacing and deleting text
## Why `run.text = ...` so often does nothing
Word splits a paragraph into runs for reasons that have nothing to do with formatting: spell-check state, language tagging, revision marks, or simply where the cursor happened to be. A placeholder you can see as one word is routinely three runs:
```
{{name}}
```
No single run contains `{{name}}`, so no assignment to `run.text` replaces it. This is the single most common surprise in working with `.docx` files.
## Replacing text
replace_text() matches against the paragraph's text as a whole, so it succeeds whether or not Word split it up. It returns how many replacements it made:
```
from docx import Document
document = Document()
paragraph = document.add_paragraph("Dear {{name}}, welcome.")
paragraph.replace_text("{{name}}", "Ada") # -> 1
paragraph.text # -> 'Dear Ada, welcome.'
```
It is available on paragraphs, on anything that contains block items, and on the document as a whole.
### Across a whole document
Document.replace_text() makes what gets searched explicit, because "replace it everywhere" means different things to different callers and getting it wrong stays invisible until someone reads the header:
```
document.replace_text("{{name}}", "Ada") # body only
document.replace_text("{{name}}", "Ada", headers_footers=True) # and those
document.replace_text("{{name}}", "Ada", footnotes=True)
document.replace_text("{{name}}", "Ada", tables=False) # skip tables
```
Tables are searched by default; headers, footers and footnotes are not.
### Regular expressions
```
import re
document.replace_text(r"\{\{(\w+)\}\}", r"<\1>", regex=True)
document.replace_text("draft", "final", regex=True, flags=re.IGNORECASE)
```
`count` limits how many replacements are made; the default of `-1` means all of them.
### What happens to formatting
The replacement takes the formatting of the run holding the first replaced character.
When a match spans runs formatted differently, the rest of the matched text is removed along with its formatting — but the runs themselves stay. A hyperlink, bookmark, comment range or field that the match only partly covers therefore keeps its structure rather than being torn in half.
## Formatting part of a paragraph
The same run-splitting problem applies when you want to embolden a phrase that is not already its own run. isolate_run() splits the runs as needed so a character range becomes exactly one run, which can then be formatted on its own:
```
paragraph = document.add_paragraph("the important part matters")
paragraph.isolate_run(4, 13).bold = True
```
Offsets are measured against Paragraph.text, so a tab counts as one character and a line break as one newline. Where the range already lines up with a run, nothing is split.
This is what replace_text() is built on.
## Deleting content
`.delete()` removes an object from its parent, and exists on paragraphs, runs, tables, rows and columns:
```
paragraph.delete()
run.delete()
table.delete()
table.rows[1].delete()
table.columns[0].delete()
```
Deletion is not just an unlink. A relationship — a hyperlink target, an image — referenced only from the deleted content is dropped with it, and a range marker left unmatched (half a bookmark, half a comment range) is removed rather than left dangling to corrupt the file.
Two cases follow Word rather than the literal XML:
- Deleting a **row** does not drop a vertically merged cell whose span began in it. The row below inherits the merge, so it continues to render.
- Deleting a **column** narrows a cell that spans it and others by one, rather than removing the cell, so the rest of the span survives.
Warning
Deleting while iterating invalidates the collection you are iterating. Take a list first:
```
for paragraph in list(document.paragraphs):
if not paragraph.text.strip():
paragraph.delete()
```
# Working with Sections
Word supports the notion of a *section*, a division of a document having the same page layout settings, such as margins and page orientation. This is how, for example, a document can contain some pages in portrait layout and others in landscape. Each section also defines the headers and footers that apply to the pages of that section.
Most Word documents have only the single section that comes by default and further, most of those have no reason to change the default margins or other page layout. But when you *do* need to change the page layout, you'll need to understand sections to get it done.
## Accessing sections
Access to document sections is provided by the `sections` property on the Document object:
```
>>> document = Document()
>>> sections = document.sections
>>> sections
>>> len(sections)
3
>>> section = sections[0]
>>> section
>>> for section in sections:
... print(section.start_type)
...
NEW_PAGE (2)
EVEN_PAGE (3)
ODD_PAGE (4)
```
It's theoretically possible for a document not to have any explicit sections, although I've yet to see this occur in the wild. If you're accessing an unpredictable population of .docx files you may want to provide for that possibility using a `len()` check or `try` block to avoid an uncaught `IndexError` exception stopping your program.
## Adding a new section
The Document.add_section method allows a new section to be started at the end of the document. Paragraphs and tables added after calling this method will appear in the new section:
```
>>> current_section = document.sections[-1] # last section in document
>>> current_section.start_type
NEW_PAGE (2)
>>> new_section = document.add_section(WD_SECTION.ODD_PAGE)
>>> new_section.start_type
ODD_PAGE (4)
```
## Section properties
The Section object has eleven properties that allow page layout settings to be discovered and specified.
### Section start type
Section.start_type describes the type of break that precedes the section:
```
>>> section.start_type
NEW_PAGE (2)
>>> section.start_type = WD_SECTION.ODD_PAGE
>>> section.start_type
ODD_PAGE (4)
```
Values of `start_type` are members of the WdSectionStart enumeration.
### Page dimensions and orientation
Three properties on Section describe page dimensions and orientation. Together these can be used, for example, to change the orientation of a section from portrait to landscape:
```
>>> section.orientation, section.page_width, section.page_height
(PORTRAIT (0), 7772400, 10058400) # (Inches(8.5), Inches(11))
>>> new_width, new_height = section.page_height, section.page_width
>>> section.orientation = WD_ORIENT.LANDSCAPE
>>> section.page_width = new_width
>>> section.page_height = new_height
>>> section.orientation, section.page_width, section.page_height
(LANDSCAPE (1), 10058400, 7772400)
```
### Page margins
Seven properties on Section together specify the various edge spacings that determine where text appears on the page:
```
>>> from docx.shared import Inches
>>> section.left_margin, section.right_margin
(1143000, 1143000) # (Inches(1.25), Inches(1.25))
>>> section.top_margin, section.bottom_margin
(914400, 914400) # (Inches(1), Inches(1))
>>> section.gutter
0
>>> section.header_distance, section.footer_distance
(457200, 457200) # (Inches(0.5), Inches(0.5))
>>> section.left_margin = Inches(1.5)
>>> section.right_margin = Inches(1)
>>> section.left_margin, section.right_margin
(1371600, 914400)
```
## Multiple text columns
A section can lay its text out in columns, as a newsletter does:
```
from docx.shared import Cm
section = document.sections[0]
section.column_count = 2
section.column_spacing = Cm(1)
```
`column_count` is the number of columns, and `column_spacing` the gap between them as a Length; `column_spacing` reads as `None` when the document does not specify one, in which case Word applies its own default.
Because this is a section property, changing the number of columns partway through a document means starting a new section at that point:
```
from docx.enum.section import WD_SECTION
two_up = document.add_section(WD_SECTION.CONTINUOUS)
two_up.column_count = 2
```
## Page borders
A page border is a section property, spelled the same way as the table, cell and paragraph borders:
```
from docx.enum.table import WD_LINE_STYLE
from docx.shared import Pt, RGBColor
borders = document.sections[0].page_borders
for edge in ("top", "bottom", "left", "right"):
borders[edge].line = WD_LINE_STYLE.SINGLE
borders[edge].size = Pt(1)
borders[edge].color = RGBColor(0x33, 0x33, 0x33)
```
Each edge exposes `line`, `size`, `color` and `space`, and `borders.clear()` removes the lot. Three settings apply to the frame as a whole rather than to one edge:
```
borders.display = "firstPage" # -- which pages get one
borders.offset_from = "text" # -- measured from the text, not the page edge
borders.z_order = "back" # -- drawn behind the page content
```
| Property | Values | Word's default when absent |
| ------------- | --------------------------------------------- | -------------------------- |
| `display` | `"allPages"`, `"firstPage"`, `"notFirstPage"` | all pages |
| `offset_from` | `"page"`, `"text"` | page |
| `z_order` | `"front"`, `"back"` | front |
These three take the attribute value itself rather than an enumeration member, because each is a two- or three-value set with no `WdEnumeration` counterpart to mirror. A value outside the set raises `ValueError` rather than writing a document Word will reject.
`offset_from` is the one that catches people out. Measured from the page edge — the default — the `space` on each edge is the distance in from the paper; measured from the text it is the distance out from the text block, which is what Word's "Measure from: Text" setting does and what a border that has to clear a header needs.
## Right-to-left and vertical text
Section.bidi sets the default base direction for the section, and Section.text_direction the default flow direction. Both are defaults a paragraph or a cell can override — see [Right-to-left and vertical text](https://toxicphreak.github.io/python-docx-ng/user/text/#right-to-left-and-vertical-text).
```
section.bidi = True
```
# Understanding pictures and other shapes
Conceptually, Word documents have two *layers*, a *text layer* and a *drawing layer*. In the text layer, text objects are flowed from left to right and from top to bottom, starting a new page when the prior one is filled. In the drawing layer, drawing objects, called *shapes*, are placed at arbitrary positions. These are sometimes referred to as *floating* shapes.
A picture is a shape that can appear in either the text or drawing layer. When it appears in the text layer it is called an *inline shape*, or more specifically, an *inline picture*.
Inline shapes are treated like a big text character (a *character glyph*). The line height is increased to accomodate the shape and the shape is wrapped to a line it will fit on width-wise, just like text. Inserting text in front of it will cause it to move to the right. Often, a picture is placed in a paragraph by itself, but this is not required. It can have text before and after it in the paragraph in which it's placed.
Both kinds are supported. Document.add_picture() adds an inline picture at the end of the document in a paragraph of its own, and Run.add_picture() places one mid-paragraph, so you can have text on either side of it or both.
For a picture in the drawing layer — one the text flows around rather than sitting on a line — see Run.add_float_picture() and [Floating images](https://toxicphreak.github.io/python-docx-ng/user/images/index.md).
## Reading the picture back
A shape hands back the image it displays through InlineShape.image and FloatingShape.image, which is `None` for a shape that is not a picture — a chart or a SmartArt diagram. See [Reading the images already in a document](https://toxicphreak.github.io/python-docx-ng/user/images/#reading-the-images-already-in-a-document).
# Understanding Styles
**Grasshopper:**\
*"Master, why doesn't my paragraph appear with the style I specified?"*
**Master:**\
*"You have come to the right page Grasshopper; read on ..."*
## What is a style in Word?
Documents communicate better when like elements are formatted consistently. To achieve that consistency, professional document designers develop a *style sheet* which defines the document element types and specifies how each should be formatted. For example, perhaps body paragraphs are to be set in 9 pt Times Roman with a line height of 11 pt, justified flush left, ragged right. When these specifications are applied to each of the elements of the document, a consistent and polished look is achieved.
A style in Word is such a set of specifications that may be applied, all at once, to a document element. Word has paragraph styles, character styles, table styles, and numbering definitions. These are applied to a paragraph, a span of text, a table, and a list, respectively.
Experienced programmers will recognize styles as a level of indirection. The great thing about those is it allows you to define something once, then apply that definition many times. This saves the work of defining the same thing over an over; but more importantly it allows you to change the definition and have that change reflected in all the places you have applied it.
## Why doesn't the style I applied show up?
This is likely to show up quite a bit until I can add some fancier features to work around it, so here it is up top.
1. When you're working in Word, there are all these styles you can apply to things, pretty good looking ones that look all the better because you don't have to make them yourself. Most folks never look further than the built-in styles.
1. Although those styles show up in the UI, they're not actually in the document you're creating, at least not until you use it for the first time. That's kind of a good thing. They take up room and there's a lot of them. The file would get a little bloated if it contained all the style definitions you could use but haven't.
1. If you apply a style using `python-docx` that's not defined in your file (in the styles.xml part if you're curious), Word just ignores it. It doesn't complain, it just doesn't change how things are formatted. I'm sure there's a good reason for this. But it can present as a bit of a puzzle if you don't understand how Word works that way.
1. When you use a style, Word adds it to the file. Once there, it stays. I imagine there's a way to get rid of it, but you have to work at it. If you apply a style, delete the content you applied it to, and then save the document; the style definition stays in the saved file.
All this adds up to the following: If you want to use a style in a document you create with `python-docx`, the document you start with must contain the style definition. Otherwise it just won't work. It won't raise an exception, it just won't work.
If you use the "default" template document, it contains the styles listed below, most of the ones you're likely to want if you're not designing your own. If you're using your own starting document, you need to use each of the styles you want at least once in it. You don't have to keep the content, but you need to apply the style to something at least once before saving the document. Creating a one-word paragraph, applying five styles to it in succession and then deleting the paragraph works fine. That's how I got the ones below into the default template :).
## Glossary
style definition\
A `` element in the styles part of a document that explicitly defines the attributes of a style.
defined style\
A style that is explicitly defined in a document. Contrast with *latent style*.
built-in style\
One of the set of 276 pre-set styles built into Word, such as "Heading 1". A built-in style can be either defined or latent. A built-in style that is not yet defined is known as a *latent style*. Both defined and latent built-in styles may appear as options in Word's style panel and style gallery.
custom style\
Also known as a *user defined style*, any style defined in a Word document that is not a built-in style. Note that a custom style cannot be a latent style.
latent style\
A built-in style having no definition in a particular document is known as a *latent style* in that document. A latent style can appear as an option in the Word UI depending on the settings in the LatentStyles object for the document.
recommended style list\
A list of styles that appears in the styles toolbox or panel when "Recommended" is selected from the "List:" dropdown box.
Style Gallery\
The selection of example styles that appear in the ribbon of the Word UI and which may be applied by clicking on one of them.
## Identifying a style
A style has three identifying properties, *name*, *style_id*, and *type*.
Each style's name property is its stable, unique identifier for access purposes.
A style's style_id is used internally to key a content object such as a paragraph to its style. However this value is generated automatically by Word and is not guaranteed to be stable across saves. In general, the style id is formed simply by removing spaces from the *localized* style name, however there are exceptions. Users of `python-docx` should generally avoid using the style id unless they are confident with the internals involved.
A style's type is set at creation time and cannot be changed.
## Built-in styles
Word comes with almost 300 so-called *built-in* styles like *Normal*, *Heading 1*, and *List Bullet*. Style definitions are stored in the *styles.xml* part of a .docx package, but built-in style definitions are stored in the Word application itself and are not written to *styles.xml* until they are actually used. This is a sensible strategy because they take up considerable room and would be largely redundant and useless overhead in every .docx file otherwise.
The fact that built-in styles are not written to the .docx package until used gives rise to the need for *latent style* definitions, explained below.
## Style Behavior
In addition to collecting a set of formatting properties, a style has five properties that specify its *behavior*. This behavior is relatively simple, basically amounting to when and where the style appears in the Word or LibreOffice UI.
The key notion to understanding style behavior is the recommended list. In the style pane in Word, the user can select which list of styles they want to see. One of these is named *Recommended* and is known as the *recommended list*. All five behavior properties affect some aspect of the style’s appearance in this list and in the style gallery.
In brief, a style appears in the recommended list if its hidden property is `False` (the default). If a style is not hidden and its quick_style property is `True`, it also appears in the style gallery. If a hidden style's unhide_when_used property is `True`, its hidden property is set `False` the first time it is used. Styles in the style lists and style gallery are sorted in priority order, then alphabetically for styles of the same priority. If a style's locked property is `True` and formatting restrictions are turned on for the document, the style will not appear in any list or the style gallery and cannot be applied to content.
## Latent styles
The need to specify the UI behavior of built-in styles not defined in *styles.xml* gives rise to the need for *latent style* definitions. A latent style definition is basically a stub style definition that has at most the five behavior attributes in addition to the style name. Additional space is saved by defining defaults for each of the behavior attributes, so only those that differ from the default need be defined and styles that match all defaults need no latent style definition.
Latent style definitions are specified using the *w:latentStyles* and *w:lsdException* elements appearing in *styles.xml*.
A latent style definition is only required for a built-in style because only a built-in style can appear in the UI without a style definition in *styles.xml*.
## Style inheritance
A style can inherit properties from another style, somewhat similarly to how Cascading Style Sheets (CSS) works. Inheritance is specified using the `base_style` attribute. By basing one style on another, an inheritance hierarchy of arbitrary depth can be formed. A style having no base style inherits properties from the document defaults.
## Paragraph styles in default template
- Normal
- Body Text
- Body Text 2
- Body Text 3
- Caption
- Heading 1
- Heading 2
- Heading 3
- Heading 4
- Heading 5
- Heading 6
- Heading 7
- Heading 8
- Heading 9
- Intense Quote
- List
- List 2
- List 3
- List Bullet
- List Bullet 2
- List Bullet 3
- List Continue
- List Continue 2
- List Continue 3
- List Number
- List Number 2
- List Number 3
- List Paragraph
- Macro Text
- No Spacing
- Quote
- Subtitle
- TOCHeading
- Title
## Character styles in default template
- Body Text Char
- Body Text 2 Char
- Body Text 3 Char
- Book Title
- Default Paragraph Font
- Emphasis
- Heading 1 Char
- Heading 2 Char
- Heading 3 Char
- Heading 4 Char
- Heading 5 Char
- Heading 6 Char
- Heading 7 Char
- Heading 8 Char
- Heading 9 Char
- Intense Emphasis
- Intense Quote Char
- Intense Reference
- Macro Text Char
- Quote Char
- Strong
- Subtitle Char
- Subtle Emphasis
- Subtle Reference
- Title Char
## Table styles in default template
- Table Normal
- Colorful Grid
- Colorful Grid Accent 1
- Colorful Grid Accent 2
- Colorful Grid Accent 3
- Colorful Grid Accent 4
- Colorful Grid Accent 5
- Colorful Grid Accent 6
- Colorful List
- Colorful List Accent 1
- Colorful List Accent 2
- Colorful List Accent 3
- Colorful List Accent 4
- Colorful List Accent 5
- Colorful List Accent 6
- Colorful Shading
- Colorful Shading Accent 1
- Colorful Shading Accent 2
- Colorful Shading Accent 3
- Colorful Shading Accent 4
- Colorful Shading Accent 5
- Colorful Shading Accent 6
- Dark List
- Dark List Accent 1
- Dark List Accent 2
- Dark List Accent 3
- Dark List Accent 4
- Dark List Accent 5
- Dark List Accent 6
- Light Grid
- Light Grid Accent 1
- Light Grid Accent 2
- Light Grid Accent 3
- Light Grid Accent 4
- Light Grid Accent 5
- Light Grid Accent 6
- Light List
- Light List Accent 1
- Light List Accent 2
- Light List Accent 3
- Light List Accent 4
- Light List Accent 5
- Light List Accent 6
- Light Shading
- Light Shading Accent 1
- Light Shading Accent 2
- Light Shading Accent 3
- Light Shading Accent 4
- Light Shading Accent 5
- Light Shading Accent 6
- Medium Grid 1
- Medium Grid 1 Accent 1
- Medium Grid 1 Accent 2
- Medium Grid 1 Accent 3
- Medium Grid 1 Accent 4
- Medium Grid 1 Accent 5
- Medium Grid 1 Accent 6
- Medium Grid 2
- Medium Grid 2 Accent 1
- Medium Grid 2 Accent 2
- Medium Grid 2 Accent 3
- Medium Grid 2 Accent 4
- Medium Grid 2 Accent 5
- Medium Grid 2 Accent 6
- Medium Grid 3
- Medium Grid 3 Accent 1
- Medium Grid 3 Accent 2
- Medium Grid 3 Accent 3
- Medium Grid 3 Accent 4
- Medium Grid 3 Accent 5
- Medium Grid 3 Accent 6
- Medium List 1
- Medium List 1 Accent 1
- Medium List 1 Accent 2
- Medium List 1 Accent 3
- Medium List 1 Accent 4
- Medium List 1 Accent 5
- Medium List 1 Accent 6
- Medium List 2
- Medium List 2 Accent 1
- Medium List 2 Accent 2
- Medium List 2 Accent 3
- Medium List 2 Accent 4
- Medium List 2 Accent 5
- Medium List 2 Accent 6
- Medium Shading 1
- Medium Shading 1 Accent 1
- Medium Shading 1 Accent 2
- Medium Shading 1 Accent 3
- Medium Shading 1 Accent 4
- Medium Shading 1 Accent 5
- Medium Shading 1 Accent 6
- Medium Shading 2
- Medium Shading 2 Accent 1
- Medium Shading 2 Accent 2
- Medium Shading 2 Accent 3
- Medium Shading 2 Accent 4
- Medium Shading 2 Accent 5
- Medium Shading 2 Accent 6
- Table Grid
# Working with Styles
This page uses concepts developed in the prior page without introduction. If a term is unfamiliar, consult the prior page [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md) for a definition.
## Access a style
Styles are accessed using the Document.styles attribute:
```
>>> document = Document()
>>> styles = document.styles
>>> styles
```
The Styles object provides dictionary-style access to defined styles by name:
```
>>> styles['Normal']
```
Note
Built-in styles are stored in a WordprocessingML file using their English name, e.g. 'Heading 1', even though users working on a localized version of Word will see native language names in the UI, e.g. 'Kop 1'. Because `python-docx` operates on the WordprocessingML file, style lookups must use the English name. A document available on this external site allows you to create a mapping between local language names and English style names:
> User-defined styles, also known as *custom styles*, are not localized and are accessed with the name exactly as it appears in the Word UI.
The Styles object is also iterable. By using the identification properties on BaseStyle, various subsets of the defined styles can be generated. For example, this code will produce a list of the defined paragraph styles:
```
>>> from docx.enum.style import WD_STYLE_TYPE
>>> styles = document.styles
>>> paragraph_styles = [
... s for s in styles if s.type == WD_STYLE_TYPE.PARAGRAPH
... ]
>>> for style in paragraph_styles:
... print(style.name)
...
Normal
Body Text
List Bullet
```
## Apply a style
The Paragraph, Run, and Table objects each have a style attribute. Assigning a style object to this attribute applies that style:
```
>>> document = Document()
>>> paragraph = document.add_paragraph()
>>> paragraph.style
>>> paragraph.style.name
'Normal'
>>> paragraph.style = document.styles['Heading 1']
>>> paragraph.style.name
'Heading 1'
```
A style name can also be assigned directly, in which case `python-docx` will do the lookup for you:
```
>>> paragraph.style = 'List Bullet'
>>> paragraph.style
>>> paragraph.style.name
'List Bullet'
```
A style can also be applied at creation time using either the style object or its name:
```
>>> paragraph = document.add_paragraph(style='Body Text')
>>> paragraph.style.name
'Body Text'
>>> body_text_style = document.styles['Body Text']
>>> paragraph = document.add_paragraph(style=body_text_style)
>>> paragraph.style.name
'Body Text'
```
## Add or delete a style
A new style can be added to the document by specifying a unique name and a style type:
```
>>> from docx.enum.style import WD_STYLE_TYPE
>>> styles = document.styles
>>> style = styles.add_style('Citation', WD_STYLE_TYPE.PARAGRAPH)
>>> style.name
'Citation'
>>> style.type
PARAGRAPH (1)
```
Use the `base_style` property to specify a style the new style should inherit formatting settings from:
```
>>> style.base_style
None
>>> style.base_style = styles['Normal']
>>> style.base_style
>>> style.base_style.name
'Normal'
```
A style can be removed from the document simply by calling its delete method:
```
>>> styles = document.styles
>>> len(styles)
10
>>> styles['Citation'].delete()
>>> len(styles)
9
```
Note
The `Style.delete` method removes the style's definition from the document. It does not affect content in the document to which that style is applied. Content having a style not defined in the document is rendered using the default style for that content object, e.g. 'Normal' in the case of a paragraph.
## Document defaults
Below every style in the inheritance chain sits `w:docDefaults`, the formatting that applies to content no style has spoken for. For many documents in the wild it is the only place the base font is set, so a style walk that stops at `Normal` reads the wrong answer:
```
document.styles.default_font.name # -> the document-wide default typeface
document.styles.default_font.size
document.styles.default_paragraph_format.space_after
document.styles.default_paragraph_format.line_spacing
```
Styles.default_font is a Font and Styles.default_paragraph_format a ParagraphFormat, so everything documented for those works here. Both are writable, and setting a document default is the broadest change you can make to a document's appearance in one statement:
```
from docx.shared import Pt
document.styles.default_font.name = "Calibri"
document.styles.default_font.size = Pt(11)
```
The inheritance order, most specific first: direct formatting on a run, then its character style, then the paragraph style, then `w:docDefaults`.
## Define character formatting
Character, paragraph, and table styles can all specify character formatting to be applied to content with that style. All the character formatting that can be applied directly to text can be specified in a style. Examples include font typeface and size, bold, italic, and underline.
Each of these three style types have a `font` attribute providing access to a Font object. A style's Font object provides properties for getting and setting the character formatting for that style.
Several examples are provided here. For a complete set of the available properties, see the Font API documentation.
The font for a style can be accessed like this:
```
>>> from docx import Document
>>> document = Document()
>>> style = document.styles['Normal']
>>> font = style.font
```
Typeface and size are set like this:
```
>>> from docx.shared import Pt
>>> font.name = 'Calibri'
>>> font.size = Pt(12)
```
Many font properties are *tri-state*, meaning they can take the values `True`, `False`, and `None`. `True` means the property is "on", `False` means it is "off". Conceptually, the `None` value means "inherit". Because a style exists in an inheritance hierarchy, it is important to have the ability to specify a property at the right place in the hierarchy, generally as far up the hierarchy as possible. For example, if all headings should be in the Arial typeface, it makes more sense to set that property on the *Heading 1* style and have *Heading 2* inherit from *Heading 1*.
Bold and italic are tri-state properties, as are all-caps, strikethrough, superscript, and many others. See the Font API documentation for a full list:
```
>>> font.bold, font.italic
(None, None)
>>> font.italic = True
>>> font.italic
True
>>> font.italic = False
>>> font.italic
False
>>> font.italic = None
>>> font.italic
None
```
Underline is a bit of a special case. It is a hybrid of a tri-state property and an enumerated value property. `True` means single underline, by far the most common. `False` means no underline, but more often `None` is the right choice if no underlining is wanted since it is rare to inherit it from a base style. The other forms of underlining, such as double or dashed, are specified with a member of the WdUnderline enumeration:
```
>>> font.underline
None
>>> font.underline = True
>>> # or perhaps
>>> font.underline = WD_UNDERLINE.DOT_DASH
```
## Define paragraph formatting
Both a paragraph style and a table style allow paragraph formatting to be specified. These styles provide access to a ParagraphFormat object via their `paragraph_format` property.
Paragraph formatting includes layout behaviors such as justification, indentation, space before and after, page break before, and widow/orphan control. For a complete list of the available properties, consult the API documentation page for the ParagraphFormat object.
Here's an example of how you would create a paragraph style having hanging indentation of 1/4 inch, 12 points spacing above, and widow/orphan control:
```
>>> from docx.enum.style import WD_STYLE_TYPE
>>> from docx.shared import Inches, Pt
>>> document = Document()
>>> style = document.styles.add_style('Indent', WD_STYLE_TYPE.PARAGRAPH)
>>> paragraph_format = style.paragraph_format
>>> paragraph_format.left_indent = Inches(0.25)
>>> paragraph_format.first_line_indent = Inches(-0.25)
>>> paragraph_format.space_before = Pt(12)
>>> paragraph_format.widow_control = True
```
## Use paragraph-specific style properties
A paragraph style has a `next_paragraph_style` property that specifies the style to be applied to new paragraphs inserted after a paragraph of that style. This is most useful when the style would normally appear only once in a sequence, such as a heading. In that case, the paragraph style can automatically be set back to a body style after completing the heading.
In the most common case (body paragraphs), subsequent paragraphs should receive the same style as the current paragraph. The default handles this case well by applying the same style if a next paragraph style is not specified.
Here's an example of how you would change the next paragraph style of the *Heading 1* style to *Body Text*:
```
>>> from docx import Document
>>> document = Document()
>>> styles = document.styles
>>> styles['Heading 1'].next_paragraph_style = styles['Body Text']
```
The default behavior can be restored by assigning `None` or the style itself:
```
>>> heading_1_style = styles['Heading 1']
>>> heading_1_style.next_paragraph_style.name
'Body Text'
>>> heading_1_style.next_paragraph_style = heading_1_style
>>> heading_1_style.next_paragraph_style.name
'Heading 1'
>>> heading_1_style.next_paragraph_style = None
>>> heading_1_style.next_paragraph_style.name
'Heading 1'
```
## Control how a style appears in the Word UI
The properties of a style fall into two categories, *behavioral properties* and *formatting properties*. Its behavioral properties control when and where the style appears in the Word UI. Its formatting properties determine the formatting of content to which the style is applied, such as the size of the font and its paragraph indentation.
There are five behavioral properties of a style:
- hidden
- unhide_when_used
- priority
- quick_style
- locked
See the [style behavior](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/#style-behavior) section in [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md) for a description of how these behavioral properties interact to determine when and where a style appears in the Word UI.
The priority property takes an integer value. The other four style behavior properties are *tri-state*, meaning they can take the value `True` (on), `False` (off), or `None` (inherit).
### Display a style in the style gallery
The following code will cause the 'Body Text' paragraph style to appear first in the style gallery:
```
>>> from docx import Document
>>> document = Document()
>>> style = document.styles['Body Text']
>>> style.hidden = False
>>> style.quick_style = True
>>> style.priorty = 1
```
### Remove a style from the style gallery
This code will remove the 'Normal' paragraph style from the style gallery, but allow it to remain in the recommended list:
```
>>> style = document.styles['Normal']
>>> style.hidden = False
>>> style.quick_style = False
```
## Working with Latent Styles
See the [builtin styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/#built-in-styles) and [latent styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/#latent-styles) sections in [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md) for a description of how latent styles define the behavioral properties of built-in styles that are not yet defined in the *styles.xml* part of a .docx file.
### Access the latent styles in a document
The latent styles in a document are accessed from the styles object:
```
>>> document = Document()
>>> latent_styles = document.styles.latent_styles
```
A LatentStyles object supports `len`, iteration, and dictionary-style access by style name:
```
>>> len(latent_styles)
161
>>> latent_style_names = [ls.name for ls in latent_styles]
>>> latent_style_names
['Normal', 'Heading 1', 'Heading 2', ... 'TOC Heading']
>>> latent_quote = latent_styles['Quote']
>>> latent_quote
>>> latent_quote.priority
29
```
### Change latent style defaults
The LatentStyles object also provides access to the default behavioral properties for built-in styles in the current document. These defaults provide the value for any undefined attributes of the \_LatentStyle definitions and to all behavioral properties of built-in styles having no explicit latent style definition. See the API documentation for the LatentStyles object for the complete set of available properties:
```
>>> latent_styles.default_to_locked
False
>>> latent_styles.default_to_locked = True
>>> latent_styles.default_to_locked
True
```
### Add a latent style definition
A new latent style can be added using the add_latent_style method on LatentStyles. This code adds a new latent style for the builtin style 'List Bullet', setting it to appear in the style gallery:
```
>>> latent_style = latent_styles['List Bullet']
KeyError: no latent style with name 'List Bullet'
>>> latent_style = latent_styles.add_latent_style('List Bullet')
>>> latent_style.hidden = False
>>> latent_style.priority = 2
>>> latent_style.quick_style = True
```
### Delete a latent style definition
A latent style definition can be deleted by calling its `delete` method:
```
>>> latent_styles['Light Grid']
>>> latent_styles['Light Grid'].delete()
>>> latent_styles['Light Grid']
KeyError: no latent style with name 'Light Grid'
```
## Which styles are in use
```
document.styles.usage() # -> a full StyleUsage report
document.styles.unused # -> the styles nothing reaches
document.styles["Quote"].in_use # -> True or False
```
"Used" is a reachability closure over every story part in the document, not a scan of the body — a style that is only ever the `basedOn` of another one is used. See [Style usage and cleanup](https://toxicphreak.github.io/python-docx-ng/user/cleanup/index.md), which also covers Styles.remove_unused() and Document.cleanup().
## Moving styles between documents
Styles.copy_style_from() copies one style with its dependency closure. Three operations build on it, for whole sets of styles at a time:
```
# -- pull a house template's styles into a generated document --
report = document.styles.import_from("house-style.dotx")
# -- push a document's styles out into a template of their own --
document.styles.extract("house.dotx", as_template=True)
# -- or just the styles.xml bytes --
xml = document.styles.extract_xml(["Heading 1", "Heading 2"])
```
See [Templates and embedded files](https://toxicphreak.github.io/python-docx-ng/user/templates/#importing-a-templates-styles) for what each of them does with a name collision and what `report` contains.
# Working with Tables
Word provides sophisticated capabilities to create tables. As usual, this power comes with additional conceptual complexity.
This complexity becomes most apparent when *reading* tables, in particular from documents drawn from the wild where there is limited or no prior knowledge as to what the tables might contain or how they might be structured.
These are some of the important concepts you'll need to understand.
## Concept: Simple (uniform) tables
```
+---+---+---+
| a | b | c |
+---+---+---+
| d | e | f |
+---+---+---+
| g | h | i |
+---+---+---+
```
The basic concept of a table is intuitive enough. You have *rows* and *columns*, and at each (row, column) position is a different *cell*. It can be described as a *grid* or a *matrix*. Let's call this concept a *uniform table*. A relational database table and a Pandas dataframe are both examples of a uniform table.
The following invariants apply to uniform tables:
- Each row has the same number of cells, one for each column.
- Each column has the same number of cells, one for each row.
## Complication 1: Merged Cells
```
+---+---+---+ +---+---+---+
| a | b | | | b | c |
+---+---+---+ + a +---+---+
| c | d | e | | | d | e |
+---+---+---+ +---+---+---+
| f | g | h | | f | g | h |
+---+---+---+ +---+---+---+
```
While very suitable for data processing, a uniform table lacks expressive power desireable for tables intended for a human reader.
Perhaps the most important characteristic a uniform table lacks is *merged cells*. It is very common to want to group multiple cells into one, for example to form a column-group heading or provide the same value for a sequence of cells rather than repeat it for each cell. These make a rendered table more *readable* by reducing the cognitive load on the human reader and make certain relationships explicit that might easily be missed otherwise.
Unfortunately, accommodating merged cells breaks both the invariants of a uniform table:
- Each row can have a different number of cells.
- Each column can have a different number of cells.
This challenges reading table contents programatically. One might naturally want to read the table into a uniform matrix data structure like a 3 x 3 "2D array" (list of lists perhaps), but this is not directly possible when the table is not known to be uniform.
## Concept: The layout grid
```
+ - + - + - +
| | | |
+ - + - + - +
| | | |
+ - + - + - +
| | | |
+ - + - + - +
```
In Word, each table has a *layout grid*.
- The layout grid is *uniform*. There is a layout position for every (layout-row, layout-column) pair.
- The layout grid itself is not visible. However it is represented and referenced by certain elements and attributes within the table XML
- Each table cell is located at a layout-grid position; i.e. the top-left corner of each cell is the top-left corner of a layout-grid cell.
- Each table cell occupies one or more whole layout-grid cells. A merged cell will occupy multiple layout-grid cells. No table cell can occupy a partial layout-grid cell.
- Another way of saying this is that every vertical boundary (left and right) of a cell aligns with a layout-grid vertical boundary, likewise for horizontal boundaries. But not all layout-grid boundaries need be occupied by a cell boundary of the table.
## Complication 2: Omitted Cells
```
+---+---+ +---+---+---+
| a | b | | a | b | c |
+---+---+---+ +---+---+---+
| c | d | | d |
+---+---+ +---+---+---+
| e | | e | f | g |
+---+ +---+---+---+
```
Word is unusual in that it allows cells to be omitted from the beginning or end (but not the middle) of a row. A typical practical example is a table with both a row of column headings and a column of row headings, but no top-left cell (position 0, 0), such as this XOR truth table.
```
+---+---+
| T | F |
+---+---+---+
| T | F | T |
+---+---+---+
| F | T | F |
+---+---+---+
```
In *python-docx*, omitted cells in a \_Row object are represented by the `.grid_cols_before` and `.grid_cols_after` properties. In the example above, for the first row, `.grid_cols_before` would equal `1` and `.grid_cols_after` would equal `0`.
Note that omitted cells are not just "empty" cells. They represent layout-grid positions that are unoccupied by a cell and they cannot be represented by a \_Cell object. This distinction becomes important when trying to produce a uniform representation (e.g. a 2D array) for an arbitrary Word table.
## Concept: *python-docx* approximates uniform tables by default
To accurately represent an arbitrary table would require a complex graph data structure. Navigating this data structure would be at least as complex as navigating the *python-docx* object graph for a table. When extracting content from a collection of arbitrary Word files, such as for indexing the document, it is common to choose a simpler data structure and *approximate* the table in that structure.
Reflecting on how a relational table or dataframe represents tabular information, a straightforward approximation would simply repeat merged-cell values for each layout-grid cell occupied by the merged cell:
```
+---+---+---+ +---+---+---+
| a | b | -> | a | a | b |
+---+---+---+ +---+---+---+
| | d | e | -> | c | d | e |
+ c +---+---+ +---+---+---+
| | f | g | -> | c | f | g |
+---+---+---+ +---+---+---+
```
This is what `_Row.cells` does by default. Conceptually:
```
>>> [tuple(c.text for c in r.cells) for r in table.rows]
[
(a, a, b),
(c, d, e),
(c, f, g),
]
```
Note this only produces a uniform "matrix" of cells when there are no omitted cells. Dealing with omitted cells requires a more sophisticated approach when maintaining column integrity is required:
```
# +---+---+
# | a | b |
# +---+---+---+
# | c | d |
# +---+---+
# | e |
# +---+
def iter_row_cell_texts(row: _Row) -> Iterator[str]:
for _ in range(row.grid_cols_before):
yield ""
for c in row.cells:
yield c.text
for _ in range(row.grid_cols_after):
yield ""
```
```
>>> [tuple(iter_row_cell_texts(r)) for r in table.rows]
[
("", "a", "b"),
("c", "d", ""),
("", "e", ""),
]
```
## Complication 3: Tables are Recursive
Further complicating table processing is their recursive nature. In Word, as in HTML, a table cell can itself include one or more tables.
These can be detected using `_Cell.tables` or `_Cell.iter_inner_content()`. The latter preserves the document order of the table with respect to paragraphs also in the cell.
## Borders
Table.borders and \_Cell.borders are mappings keyed by edge name:
```
from docx.enum.table import WD_LINE_STYLE
from docx.shared import Pt, RGBColor
table = document.add_table(rows=2, cols=2)
table.borders["top"].line = WD_LINE_STYLE.SINGLE
table.borders["top"].size = Pt(1)
table.borders["top"].color = RGBColor(0xFF, 0x00, 0x00)
```
A table admits `left`, `right`, `top`, `bottom`, and `insideH` and `insideV` for the horizontal and vertical borders *between* its cells. A cell admits the same four edges plus the two diagonals, `tl2br` and `tr2bl`:
```
cell = table.cell(0, 0)
cell.borders["bottom"].line = WD_LINE_STYLE.DOUBLE
```
A border set on a cell takes precedence over the table border at the same edge.
Each edge exposes `line` (a WD_LINE_STYLE member), `size` (a Length), `color` (an RGBColor, not a hex string) and `space`. Setting `line` to `WD_LINE_STYLE.NONE` removes the border.
## Table width, indent and cell margins
Table.width is the table's *preferred* width — Word treats it as a request and may narrow the table to fit its container. It takes either a Length or a percentage:
```
from docx.shared import Inches, Pct
table.width = Inches(4)
table.width = Pct(50) # -> half the container width
table.width = None # -> auto-fit, which is the default
```
Pct is deliberately *not* a `Length`. Every `Length` unit is absolute and reduces to EMU; a percentage does not, and cannot be converted to one without knowing what it is a percentage of. Reading `width` back gives whichever of the two the table actually carries.
Table.indent moves the whole table in from the margin, and Table.cell_margins sets the default padding inside every cell:
```
table.indent = Inches(0.5)
table.cell_margins.left = Inches(0.1)
table.cell_margins.top = Inches(0.05)
table.cell_margins.clear() # -> back to inherited
```
The mapping admits `top`, `bottom`, `left`, `right` and the direction-relative `start` and `end`. These are the table-wide defaults; a cell's own `w:tcMar` overrides them where it has one.
## Which parts of a table style apply
A table style can define different formatting for the first row, the last row, the first and last columns, and alternating bands. Which of those *apply* is not part of the style — it is a set of flags on the table, the ones Word shows as the "Table Style Options" checkboxes:
```
table.look.first_row = True # -- header row formatting on
table.look.horizontal_banding = True # -- alternating row shading on
table.look.last_column = False
```
Table.look exposes `first_row`, `last_row`, `first_column`, `last_column`, `horizontal_banding` and `vertical_banding`. Each is `True` or `False` — never `None`, because `w:tblLook` has a defined default for each flag rather than an inherited one.
Note
`w:tblLook` carries both modern per-flag attributes and a legacy `@w:val` bitmask, and older versions of Word read the bitmask. Setting a flag rewrites both, as Word does, so the table looks the same wherever it is opened.
## Row properties
```
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.shared import Inches
row = table.rows[0]
row.repeat_as_header = True # -- "Repeat Header Rows"
row.hidden = False
row.alignment = WD_TABLE_ALIGNMENT.CENTER
row.cell_spacing = Inches(0.02)
row.width_before = Inches(0.5)
row.width_after = Inches(0.5)
```
repeat_as_header is the useful one: it is what makes a header row reappear at the top of every page a long table spans. All of these are tri-state where the XML is — `None` means the value is inherited — and \_Row.height, `height_rule` and \_Row.dont_split round out `w:trPr`.
`width_before` and `width_after` are the widths of the grid positions a row leaves unpopulated, the companions to the `grid_cols_before` and `grid_cols_after` counts described above.
## Text direction in a cell
\_Cell.text_direction rotates the text in a cell, which is how a narrow column gets a readable heading:
```
from docx.enum.text import WD_TEXT_DIRECTION
table.cell(0, 1).text_direction = WD_TEXT_DIRECTION.BT_LR # -- bottom-to-top
```
See [Right-to-left and vertical text](https://toxicphreak.github.io/python-docx-ng/user/text/#right-to-left-and-vertical-text) for the full set of WD_TEXT_DIRECTION values.
## Alternative text
A table carries the same two alt-text values Word's "Alt Text" pane writes for a picture, and they matter for the same reason — an accessibility check on a generated document flags a table without them:
```
table = document.add_table(rows=2, cols=2)
table.title = "Quarterly revenue"
table.description = "Revenue by region for Q1 through Q4 2026, in thousands of euro."
```
Both are read/write on Table and both are `None` when unset; assigning `None` removes them. They are stored as `w:tblCaption` and `w:tblDescription` and are never rendered — this is metadata read by assistive technology, not a visible caption above or below the table.
They can also be given when the table is created, which saves the round trip and matches the way add_picture() takes them:
```
table = document.add_table(
rows=2,
cols=2,
style="Light Grid Accent 1",
title="Quarterly revenue",
description="Revenue by region for Q1 through Q4 2026, in thousands of euro.",
)
```
Both are keyword-only, both default to `None`, and omitting them writes nothing. \_Cell.add_table() and BlockItemContainer.add_table() take them too.
## Captions
\_Cell.add_caption() puts a self-renumbering caption inside a cell. For a caption above or below a table, use Document.add_caption() — see [Captions](https://toxicphreak.github.io/python-docx-ng/user/fields/#captions).
# Templates, styles across documents, embedded files and macros
## Word templates
A `.dotx` or `.dotm` template holds exactly the same markup as a document. It differs only in the content type of its main part, which is what tells Word to start a *new* document from it rather than open it for editing.
Templates open like anything else:
```
from docx import Document
document = Document("house-style.dotx")
document.is_template # -> True
```
save() keeps whichever it already is, so a template opened and saved is still a template. `as_template` overrides that, which is how you generate a document from a template — or turn a document into one:
```
document.save("report.docx", as_template=False) # a document from a template
document.save("house-style.dotx", as_template=True) # a template from a document
```
Macro-enabled input stays macro-enabled either way, so a `.dotm` saved with `as_template=False` is a `.docm`.
Note
This sets the content type. It does not choose the file extension for you — pass a name whose extension matches, or Word will complain about the mismatch.
## Copying a style between documents
Applying a style by name fails with `KeyError: no style with name 'Callout'` whenever the target document's style part lacks it, which is routine when content is assembled from several sources. copy_style_from() brings one across:
```
template = Document("house-style.dotx")
report = Document()
callout = template.styles["Callout"]
report.styles.copy_style_from(callout)
report.add_paragraph("Mind the gap", style="Callout")
```
**The dependency closure is the point.** A style is not a self-contained object: `w:basedOn` names the style it inherits from, `w:next` the style for the following paragraph, and `w:link` the paired character or paragraph style. Copying one `w:style` element by hand gives a style whose `basedOn` target is missing, which then renders as if it inherited from Normal. Those are followed and copied too, unless you say otherwise:
```
report.styles.copy_style_from(callout, include_dependencies=False)
```
A list style references `numbering.xml`, so `include_numbering` (on by default) copies the `w:num` and `w:abstractNum` behind it and rewrites the reference to the new id.
### Name collisions
```
report.styles.copy_style_from(callout, name="House Callout")
report.styles.copy_style_from(callout, on_collision="overwrite")
```
`on_collision` decides what happens when the target already has a style of that name:
| Value | Behaviour |
| ------------------ | ----------------------------------------------- |
| `"skip"` (default) | leave the existing style alone and return it |
| `"overwrite"` | replace its definition |
| `"rename"` | copy under a free name — "Callout 2", and so on |
| `"raise"` | raise `ValueError` |
Warning
**Theme fonts are not carried over.** A style referencing `w:asciiTheme` resolves against *this* document's theme part, so a copied style can legitimately look different in its new home.
## Importing a template's styles
`copy_style_from()` moves one style. Two operations built on it move whole sets, which is what a house template usually calls for:
```
report = Document()
result = report.styles.import_from("house-style.dotx")
# -> {"Callout": "added", "Heading 1": "skipped", ...}
```
Styles.import_from() accepts a path, a stream or an already-open `Document` — a `.dotx` opens without special handling, since a template holds the same main part as a document. It returns a report keyed by UI name, saying what it did with each: `"added"`, `"replaced"` or `"skipped"`.
```
report.styles.import_from("house-style.dotx", ["Callout", "Sidebar"])
report.styles.import_from("house-style.dotx", overwrite=True)
report.styles.import_from("house-style.dotx", include_latent=True)
```
Without `overwrite`, a name the destination already defines is skipped and reported as such — the existing definition wins, which is what "import these styles into my document" almost always means. `include_latent` also brings the source's `w:latentStyles` exceptions across, and is off by default because that changes which of Word's built-ins appear in the destination's style gallery.
The other direction pulls styles *out* of a document into a template of their own:
```
added = report.styles.extract("house.dotx", as_template=True)
added = report.styles.extract("headings.docx", ["Heading 1", "Heading 2"])
xml = report.styles.extract_xml(["Heading 1"]) # -> just the styles.xml bytes
```
Styles.extract() writes an otherwise empty document carrying the named styles and their dependency closure, and returns the names it added. Naming nothing extracts every style the document defines. The extract starts from a template whose own unused styles have been pruned, so what comes out is the styles you named plus the handful the closure keeps alive — `Normal`, `Default Paragraph Font` and the other defaults — rather than those plus the 168 the bundled template ships.
`as_template=True` writes a `.dotx` — the same content with the template content type, so Word treats it as a template rather than a document.
## Embedded OLE objects
An OLE object is a whole file carried *inside* the document and shown as an icon or a preview image that opens the original application on double-click. This is a different thing from an `altChunk`: an `altChunk` is dissolved into the document when Word opens the file, whereas an embedded object stays a distinct file forever.
The read side matters on its own. A document with attachments embedded in it previously gave no way to discover that they exist, let alone get them out:
```
from pathlib import Path
for obj in document.embedded_objects:
print(obj.prog_id, obj.content_type, obj.filename)
if obj.blob is not None:
Path(obj.filename or "attachment").write_bytes(obj.blob)
```
Document.embedded_objects covers the body and Run.embedded_objects one run. Each EmbeddedObject offers:
| | |
| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `prog_id` | the application Word launches, e.g. `"Excel.Sheet.12"` |
| `blob` | the bytes of the embedded file, or `None` |
| `content_type` | the content type of the embedded part |
| `filename` | the basename of the part it landed in, e.g. `"oleObject1.bin"` |
| `is_linked` | `True` when the object *links* to an external file instead of embedding it, in which case there are no bytes in the package |
| `shows_icon` | `True` when Word shows an icon rather than a preview |
| `image` | the icon or preview image Word displays |
| `embedded_part` | the package part itself |
Note
OOXML does not record the original file name of an embedded object. `filename` is the name of the part it was stored in, which is what a caller extracting it has to work with.
Writing one takes an icon, and the icon is required:
```
run = document.add_paragraph().add_run()
run.add_embedded_object(
"budget.xlsx",
icon="excel-icon.png",
prog_id="Excel.Sheet.12",
)
```
Word cannot render the embedded file itself, so without an image there is nothing to draw where the object sits. `prog_id` defaults to `"Package"`, the generic value Word uses for a file it has no better name for — an object whose `ProgID` names no installed application is one Word displays but cannot open, so pass the right one when you know it. `width` and `height` default to the icon's own size.
The visual is VML, not DrawingML, because that is what Word writes for an OLE object.
## Macros
A macro-enabled document keeps its VBA project as a single opaque blob, `word/vbaProject.bin`:
```
document = Document("macros.docm")
document.has_macros # -> True
len(document.vba_project) # -> the project bytes
```
Reading, transplanting and stripping one are all expressible:
```
# -- move a project into a generated document --
generated = Document()
generated.vba_project = Document("macros.docm").vba_project
generated.save("generated.docm")
# -- strip the macros out of a document you received --
received = Document("received.docm")
del received.vba_project # -- or: received.vba_project = None
received.save("safe.docx")
received.remove_vba_project() # -> how many were removed, 0 or 1
```
Assigning or removing a project **switches the main part's content type with it**, which is the part that is easy to get wrong by hand: Word ignores macros in a document that does not claim to be macro-enabled, and warns about macros in a document that claims to be macro-enabled but is not. A template switches to the macro-enabled *template* type rather than the document one.
Note
The blob is not parsed. The project is an OLE compound file with compressed module streams inside it; reading the source of a macro is a separate matter and is not supported.
## Embedding another document
An `altChunk` embeds a whole file — HTML, RTF, another `.docx` — and lets Word import it on open:
```
document = Document()
document.add_alt_chunk(b"
Report
Generated.
", content_type="text/html")
```
`chunk` may be bytes, a path, or a file-like object open for binary read. Note that a `str` is read as a **path**, not as content — pass content as bytes, as above.
`content_type` must be right, because Word picks its importer from it:
| Content type | Format |
| ------------------------------------------------------------------------- | ---------- |
| `text/html` | HTML |
| `text/plain` | plain text |
| `application/rtf` | RTF |
| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | `.docx` |
```
with open("appendix.docx", "rb") as f:
document.add_alt_chunk(
f,
content_type=(
"application/vnd.openxmlformats-officedocument"
".wordprocessingml.document"
),
)
```
Warning
**Word performs the import when it opens the document, so the embedded content is not visible to this library.** Its paragraphs and tables do not appear in Document.paragraphs, Document.tables or iter_inner_content(), and nothing here can style it. It is a handover to Word, not a merge.
Readers other than Word may ignore `altChunk` entirely.
Document.alt_chunks lists what has been embedded.
# Working with Text
To work effectively with text, it's important to first understand a little about block-level elements like paragraphs and inline-level objects like runs.
## Block-level vs. inline text objects
The paragraph is the primary block-level object in Word.
A block-level item flows the text it contains between its left and right edges, adding an additional line each time the text extends beyond its right boundary. For a paragraph, the boundaries are generally the page margins, but they can also be column boundaries if the page is laid out in columns, or cell boundaries if the paragraph occurs inside a table cell.
A table is also a block-level object.
An inline object is a portion of the content that occurs inside a block-level item. An example would be a word that appears in bold or a sentence in all-caps. The most common inline object is a *run*. All content within a block container is inside of an inline object. Typically, a paragraph contains one or more runs, each of which contain some part of the paragraph's text.
The attributes of a block-level item specify its placement on the page, such items as indentation and space before and after a paragraph. The attributes of an inline item generally specify the font in which the content appears, things like typeface, font size, bold, and italic.
## Paragraph properties
A paragraph has a variety of properties that specify its placement within its container (typically a page) and the way it divides its content into separate lines.
In general, it's best to define a *paragraph style* collecting these attributes into a meaningful group and apply the appropriate style to each paragraph, rather than repeatedly apply those properties directly to each paragraph. This is analogous to how Cascading Style Sheets (CSS) work with HTML. All the paragraph properties described here can be set using a style as well as applied directly to a paragraph.
The formatting properties of a paragraph are accessed using the ParagraphFormat object available using the paragraph's paragraph_format property.
### Horizontal alignment (justification)
Also known as *justification*, the horizontal alignment of a paragraph can be set to left, centered, right, or fully justified (aligned on both the left and right sides) using values from the enumeration `WdParagraphAlignment`:
```
>>> from docx.enum.text import WD_ALIGN_PARAGRAPH
>>> document = Document()
>>> paragraph = document.add_paragraph()
>>> paragraph_format = paragraph.paragraph_format
>>> paragraph_format.alignment
None # indicating alignment is inherited from the style hierarchy
>>> paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
>>> paragraph_format.alignment
CENTER (1)
```
### Indentation
Indentation is the horizontal space between a paragraph and edge of its container, typically the page margin. A paragraph can be indented separately on the left and right side. The first line can also have a different indentation than the rest of the paragraph. A first line indented further than the rest of the paragraph has *first line indent*. A first line indented less has a *hanging indent*.
Indentation is specified using a Length value, such as Inches, Pt, or Cm. Negative values are valid and cause the paragraph to overlap the margin by the specified amount. A value of `None` indicates the indentation value is inherited from the style hierarchy. Assigning `None` to an indentation property removes any directly-applied indentation setting and restores inheritance from the style hierarchy:
```
>>> from docx.shared import Inches
>>> paragraph = document.add_paragraph()
>>> paragraph_format = paragraph.paragraph_format
>>> paragraph_format.left_indent
None # indicating indentation is inherited from the style hierarchy
>>> paragraph_format.left_indent = Inches(0.5)
>>> paragraph_format.left_indent
457200
>>> paragraph_format.left_indent.inches
0.5
```
Right-side indent works in a similar way:
```
>>> from docx.shared import Pt
>>> paragraph_format.right_indent
None
>>> paragraph_format.right_indent = Pt(24)
>>> paragraph_format.right_indent
304800
>>> paragraph_format.right_indent.pt
24.0
```
First-line indent is specified using the first_line_indent property and is interpreted relative to the left indent. A negative value indicates a hanging indent:
```
>>> paragraph_format.first_line_indent
None
>>> paragraph_format.first_line_indent = Inches(-0.25)
>>> paragraph_format.first_line_indent
-228600
>>> paragraph_format.first_line_indent.inches
-0.25
```
### Indentation in character units
Word can express an indent as a number of characters rather than an absolute distance, which is what East Asian typesetting conventions expect and what Word's own dialogue offers when the document language calls for it. Those values are a separate set of properties, because they are a different unit and the two cannot both apply:
```
>>> paragraph_format.first_line_indent_chars = 200 # -> two characters
>>> paragraph_format.left_indent_chars = 100 # -> one character
>>> paragraph_format.right_indent_chars
None
```
Values are in **hundredths of a character**, matching the XML — `200` is two characters. Setting a character-unit property clears its twips counterpart and vice versa, so the two can never disagree about the same edge.
The absolute properties also read the `w:start` and `w:end` spellings Word writes in recent files, not only the older `w:left` and `w:right`, so a document produced by a current version of Word reports the indents it actually has.
### Tab stops
A tab stop determines the rendering of a tab character in the text of a paragraph. In particular, it specifies the position where the text following the tab character will start, how it will be aligned to that position, and an optional leader character that will fill the horizontal space spanned by the tab.
The tab stops for a paragraph or style are contained in a TabStops object accessed using the tab_stops property on ParagraphFormat:
```
>>> tab_stops = paragraph_format.tab_stops
>>> tab_stops
```
A new tab stop is added using the add_tab_stop method:
```
>>> tab_stop = tab_stops.add_tab_stop(Inches(1.5))
>>> tab_stop.position
1371600
>>> tab_stop.position.inches
1.5
```
Alignment defaults to left, but may be specified by providing a member of the WdTabAlignment enumeration. The leader character defaults to spaces, but may be specified by providing a member of the WdTabLeader enumeration:
```
>>> from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER
>>> tab_stop = tab_stops.add_tab_stop(Inches(1.5), WD_TAB_ALIGNMENT.RIGHT, WD_TAB_LEADER.DOTS)
>>> print(tab_stop.alignment)
RIGHT (2)
>>> print(tab_stop.leader)
DOTS (1)
```
Existing tab stops are accessed using sequence semantics on TabStops:
```
>>> tab_stops[0]
```
More details are available in the TabStops and TabStop API documentation
### Paragraph spacing
The space_before and space_after properties control the spacing between subsequent paragraphs, controlling the spacing before and after a paragraph, respectively. Inter-paragraph spacing is *collapsed* during page layout, meaning the spacing between two paragraphs is the maximum of the *space_after* for the first paragraph and the *space_before* of the second paragraph. Paragraph spacing is specified as a Length value, often using Pt:
```
>>> paragraph_format.space_before, paragraph_format.space_after
(None, None) # inherited by default
>>> paragraph_format.space_before = Pt(18)
>>> paragraph_format.space_before.pt
18.0
>>> paragraph_format.space_after = Pt(12)
>>> paragraph_format.space_after.pt
12.0
```
### Line spacing
Line spacing is the distance between subsequent baselines in the lines of a paragraph. Line spacing can be specified either as an absolute distance or relative to the line height (essentially the point size of the font used). A typical absolute measure would be 18 points. A typical relative measure would be double-spaced (2.0 line heights). The default line spacing is single-spaced (1.0 line heights).
Line spacing is controlled by the interaction of the line_spacing and line_spacing_rule properties. line_spacing is either a Length value, a (small-ish) `float`, or None. A Length value indicates an absolute distance. A `float` indicates a number of line heights. `None` indicates line spacing is inherited. line_spacing_rule is a member of the WdLineSpacing enumeration or `None`:
```
>>> from docx.shared import Length
>>> paragraph_format.line_spacing
None
>>> paragraph_format.line_spacing_rule
None
>>> paragraph_format.line_spacing = Pt(18)
>>> isinstance(paragraph_format.line_spacing, Length)
True
>>> paragraph_format.line_spacing.pt
18.0
>>> paragraph_format.line_spacing_rule
EXACTLY (4)
>>> paragraph_format.line_spacing = 1.75
>>> paragraph_format.line_spacing
1.75
>>> paragraph_format.line_spacing_rule
MULTIPLE (5)
```
### Spacing in line units
As with indentation, Word can express paragraph spacing as a number of lines rather than an absolute distance:
```
>>> paragraph_format.space_before_lines = 100 # -> one line
>>> paragraph_format.space_after_lines = 50 # -> half a line
```
Values are in **hundredths of a line**. These are `w:spacing/@w:beforeLines` and `@w:afterLines`, and Word applies them in preference to the absolute values when both are present — so as with the character units, setting one clears the other.
### Paragraph borders
A paragraph's borders are spelled the same way as a table's or a cell's — a mapping keyed by edge name:
```
from docx.enum.table import WD_LINE_STYLE
from docx.shared import Pt, RGBColor
paragraph_format.borders["bottom"].line = WD_LINE_STYLE.SINGLE
paragraph_format.borders["bottom"].size = Pt(1)
paragraph_format.borders["bottom"].color = RGBColor(0x99, 0x99, 0x99)
```
A paragraph with only a bottom border is how Word draws a horizontal rule, which is the common reason to want this.
The edges are `top`, `bottom`, `left`, `right`, plus two a table does not have:
- `between` — drawn between *consecutive* paragraphs that carry the same border setting, not around each one.
- `bar` — a vertical line at the outer edge, used to mark changed text.
Each edge exposes `line`, `size`, `color` and `space`, and setting `line` to `WD_LINE_STYLE.NONE` removes the border. For borders around a whole page, see [Page borders](https://toxicphreak.github.io/python-docx-ng/user/sections/#page-borders).
### The paragraph mark
A paragraph ends with a mark — the ¶ Word shows with formatting marks turned on — and that mark has run properties of its own, stored in `w:pPr/w:rPr`. They are what an *empty* paragraph is formatted with, because there is no run in it to carry formatting:
```
paragraph = document.add_paragraph() # -- empty
paragraph.paragraph_format.mark_font.size = Pt(4)
```
That is the way to make a blank spacer paragraph small, and it is the only place the formatting of an empty paragraph lives. mark_font is a full Font, so everything in [Apply character formatting](#apply-character-formatting) applies to it. It also affects the mark of a *non*-empty paragraph, which is what decides how tall the last line is.
### Pagination properties
Four paragraph properties, keep_together, keep_with_next, page_break_before, and widow_control control aspects of how the paragraph behaves near page boundaries.
keep_together causes the entire paragraph to appear on the same page, issuing a page break before the paragraph if it would otherwise be broken across two pages.
keep_with_next keeps a paragraph on the same page as the subsequent paragraph. This can be used, for example, to keep a section heading on the same page as the first paragraph of the section.
page_break_before causes a paragraph to be placed at the top of a new page. This could be used on a chapter heading to ensure chapters start on a new page.
widow_control breaks a page to avoid placing the first or last line of the paragraph on a separate page from the rest of the paragraph.
All four of these properties are *tri-state*, meaning they can take the value `True`, `False`, or `None`. `None` indicates the property value is inherited from the style hierarchy. `True` means "on" and `False` means "off":
```
>>> paragraph_format.keep_together
None # all four inherit by default
>>> paragraph_format.keep_with_next = True
>>> paragraph_format.keep_with_next
True
>>> paragraph_format.page_break_before = False
>>> paragraph_format.page_break_before
False
```
## Apply character formatting
Character formatting is applied at the Run level. Examples include font typeface and size, bold, italic, and underline.
A Run object has a read-only font property providing access to a Font object. A run's Font object provides properties for getting and setting the character formatting for that run.
Several examples are provided here. For a complete set of the available properties, see the Font API documentation.
The font for a run can be accessed like this:
```
>>> from docx import Document
>>> document = Document()
>>> run = document.add_paragraph().add_run()
>>> font = run.font
```
Typeface and size are set like this:
```
>>> from docx.shared import Pt
>>> font.name = 'Calibri'
>>> font.size = Pt(12)
```
Many font properties are *tri-state*, meaning they can take the values `True`, `False`, and `None`. `True` means the property is "on", `False` means it is "off". Conceptually, the `None` value means "inherit". A run exists in the style inheritance hierarchy and by default inherits its character formatting from that hierarchy. Any character formatting directly applied using the Font object overrides the inherited values.
Bold and italic are tri-state properties, as are all-caps, strikethrough, superscript, and many others. See the Font API documentation for a full list:
```
>>> font.bold, font.italic
(None, None)
>>> font.italic = True
>>> font.italic
True
>>> font.italic = False
>>> font.italic
False
>>> font.italic = None
>>> font.italic
None
```
Underline is a bit of a special case. It is a hybrid of a tri-state property and an enumerated value property. `True` means single underline, by far the most common. `False` means no underline, but more often `None` is the right choice if no underlining is wanted. The other forms of underlining, such as double or dashed, are specified with a member of the WdUnderline enumeration:
```
>>> font.underline
None
>>> font.underline = True
>>> # or perhaps
>>> font.underline = WD_UNDERLINE.DOT_DASH
```
### Font color
Each Font object has a ColorFormat object that provides access to its color, accessed via its read-only color property.
Apply a specific RGB color to a font:
```
>>> from docx.shared import RGBColor
>>> font.color.rgb = RGBColor(0x42, 0x24, 0xE9)
```
A font can also be set to a theme color by assigning a member of the MsoThemeColorIndex enumeration:
```
>>> from docx.enum.dml import MSO_THEME_COLOR
>>> font.color.theme_color = MSO_THEME_COLOR.ACCENT_1
```
A font's color can be restored to its default (inherited) value by assigning `None` to either the rgb or theme_color attribute of ColorFormat:
```
>>> font.color.rgb = None
```
Determining the color of a font begins with determining its color type:
```
>>> font.color.type
RGB (1)
```
The value of the type property can be a member of the MsoColorType enumeration or None. *MSO_COLOR_TYPE.RGB* indicates it is an RGB color. *MSO_COLOR_TYPE.THEME* indicates a theme color. *MSO_COLOR_TYPE.AUTO* indicates its value is determined automatically by the application, usually set to black. (This value is relatively rare.) `None` indicates no color is applied and the color is inherited from the style hierarchy; this is the most common case.
When the color type is *MSO_COLOR_TYPE.RGB*, the rgb property will be an RGBColor value indicating the RGB color:
```
>>> font.color.rgb
RGBColor(0x42, 0x24, 0xe9)
```
When the color type is *MSO_COLOR_TYPE.THEME*, the theme_color property will be a member of MsoThemeColorIndex indicating the theme color:
```
>>> font.color.theme_color
ACCENT_1 (5)
```
## East Asian and complex-script typefaces
Word stores up to four typefaces for a run, one per script, and applies whichever matches the characters being rendered. Font.name is the Latin one; the others have their own properties:
```
font = paragraph.add_run("mixed script text").font
font.name = "Calibri" # Latin
font.east_asian_name = "MS Mincho"
font.complex_script_name = "Arial"
font.high_ansi_name = "Calibri"
```
Complex scripts also carry their own size, which is why a run can render at one size in Latin and another in Arabic or Hebrew:
```
from docx.shared import Pt
font.size = Pt(11)
font.cs_size = Pt(13)
```
## Character scaling
Horizontal scaling stretches or condenses the glyphs, as a whole percentage of normal width:
```
font.scaling = 150 # half again as wide
font.scaling = 80 # condensed
font.scaling = None # inherit from the style hierarchy
```
## Shading
Shading fills the background behind text. It exists on a run and on a paragraph, and takes an RGB hex string:
```
font.shading_fill = "FFFF00"
paragraph.paragraph_format.shading_fill = "EEEEEE"
```
Word draws a *pattern* in a foreground colour over that fill. The usual case is no pattern at all, `WD_SHADING_PATTERN.CLEAR`, which is what the two assignments above produce and what leaves the fill as a plain background. The percentage patterns are how Word produces a tint of one colour over another:
```
from docx.enum.text import WD_SHADING_PATTERN
font.shading_fill = "FFFFFF" # -- background --
font.shading_color = "FF0000" # -- pattern foreground --
font.shading_pattern = WD_SHADING_PATTERN.PCT_25 # -- 25% red over white --
```
Setting `shading_pattern` to `None` removes the shading entirely, as does setting `shading_fill` to `None`.
Note
A shading pattern is valid with no fill — `` is what Word writes for several of its Shading presets. Reading `shading_fill` on such a run returns `None` rather than raising.
Note
In 0.9.x, Font.highlight_color fell back to reading `w:shd`. It no longer does: it is strictly a WD_COLOR_INDEX member — Word's highlighter pen, which has a fixed palette — and `shading_fill` is the arbitrary-colour fill.
## Theme fonts
A run's typeface can be set to a *theme token* rather than a font name — `minorHAnsi` for body text, `majorHAnsi` for headings — in which case the concrete font comes from the document's theme. Font.name reports `None` for such a run, because there is no font name in the run to report:
```
run.font.theme = "minorHAnsi"
run.font.name # -> None
run.font.theme # -> "minorHAnsi"
run.font.theme_typeface # -> "Cambria"
```
Font.theme_typeface resolves the token through the theme part, and for a document whose fonts come only from its theme this is the only way to find out what the text is actually rendered in. It is `None` when the run carries no token, when the theme leaves that slot empty, or when the `Font` was built over a bare element with no part behind it.
The theme itself is Document.theme:
```
theme = document.theme
theme.name # -> "Office Theme"
theme.minor_font.latin # -> "Cambria"
theme.major_font.latin # -> "Calibri"
theme.minor_font.east_asian # -> None, the default theme leaves it empty
theme.minor_font.complex_script # -> None
theme.typeface("minorHAnsi") # -> "Cambria"
```
The twelve theme colours are there too, keyed by the slot names as they appear in the XML — `dk1`, `lt1`, `dk2`, `lt2`, `accent1` through `accent6`, `hlink` and `folHlink`:
```
theme.color("accent1") # -> RGBColor(0x4F, 0x81, 0xBD)
theme.colors # -> the twelve, as a dict, in schema order
```
A slot name that does not exist raises `ValueError`; a slot the theme omits gives `None`. A system colour such as `dk1` reports the value the producing application last resolved it to, which is the only concrete value available off that operating system.
Note
Document.theme is `None` for a document with no theme part, and — unlike the styles and settings parts — one is never created on demand. A theme is a design the document was authored against; an empty one synthesised on the spot would answer the typeface question with a fiction.
## Right-to-left and vertical text
Two separate things, often confused:
**Base direction** is whether a paragraph reads right-to-left. It decides where the first character goes, which way punctuation faces, and which edge `start` and `end` mean:
```
paragraph.paragraph_format.bidi = True
```
**Flow direction** is which way the lines themselves run, and it rotates the text:
```
from docx.enum.text import WD_TEXT_DIRECTION
paragraph.paragraph_format.text_direction = WD_TEXT_DIRECTION.TB_RL
```
| WD_TEXT_DIRECTION | Flow |
| ----------------- | --------------------------------------------------------------------- |
| `LR_TB` | left to right, then top to bottom — the default |
| `TB_RL` | top to bottom, then right to left — rotates the text 90° clockwise |
| `BT_LR` | bottom to top, then left to right — rotates it 90° anticlockwise |
| `LR_TB_V` | left to right, then top to bottom, rotating each East Asian character |
| `TB_RL_V` | top to bottom, then right to left, with each character upright |
| `TB_LR_V` | top to bottom, then left to right, Mongolian vertical layout |
Both exist at three levels, and the more specific wins:
```
document.sections[0].bidi = True # -- the section default
document.sections[0].text_direction = WD_TEXT_DIRECTION.TB_RL
paragraph.paragraph_format.text_direction = ... # -- one paragraph
table.cell(0, 0).text_direction = ... # -- one cell
```
All of them are `None` when the value is inherited.
## Equations
An equation is OMML — `m:oMath` — a markup language of its own that shares nothing with WordprocessingML but the file it lives in. Reading is supported; there is no builder.
```
for equation in document.math:
print(equation.text, equation.is_display)
```
Document.math, BlockItemContainer.math and Paragraph.math each return the Math objects in document order:
- `.text` — the characters of the equation, in reading order, with the structure flattened away. A fraction reads as its numerator then its denominator; there is no LaTeX here.
- `.xml` — the OMML itself, which is what to use if you need the structure.
- `.is_display` — `True` for a display equation in a `m:oMathPara` of its own, `False` for one inline in a sentence.
Note
**Equation text is deliberately not part of Paragraph.text.**
Including it would describe the document more truthfully. But replace_text() and the run-isolating machinery under it measure offsets against `Paragraph.text` and can only cut at run boundaries. Text they cannot reach would silently mis-target every replacement after the first equation in a paragraph, and a wrong edit is worse than a missing character.
Read `paragraph.math` when you want the equations, and `paragraph.text` when you want what is safely editable.
# Watermarks
A watermark is the faint "DRAFT" or "CONFIDENTIAL" behind the content, or a logo sitting under it. Word implements one as a shape anchored in the *header*, which is what makes it appear on every page.
## Adding a text watermark
```
from docx import Document
document = Document()
document.add_text_watermark("DRAFT")
```
That is the whole of the common case. The defaults reproduce Word's own "Semitransparent" watermark: light grey Calibri, rotated 315°, sized to the page.
Every section is covered, and within each section the default, first-page and even-page headers alike — so the watermark does not vanish on a page that uses a different header. A header shared between sections is written to once.
Appearance is controlled by keyword:
```
document.add_text_watermark(
"CONFIDENTIAL",
font="Arial",
color="FF0000",
angle=0,
opacity=0.4,
bold=True,
)
```
- `color` is an RGB hex string, without a leading `#`
- `angle` is degrees; `315` is Word's diagonal, `0` is horizontal
- `opacity` sets true VML transparency, which is subtler than picking a pale colour
- `font_size`, `width` and `height` take a Length; leaving `font_size` unset scales the text to the box
## An image watermark
```
document.add_image_watermark("logo.png", scale=0.75)
```
`washout` is on by default and applies Word's brightness-and-contrast correction — that is what makes a logo read as a background rather than sitting opaquely over the text. Turn it off for an image that is already faint:
```
document.add_image_watermark("logo.png", washout=False)
```
## One section only
The same three methods exist on Section, which is how a watermark is applied to part of a document rather than all of it:
```
document.sections[0].add_text_watermark("DRAFT")
```
## Reading and removing
Document.watermarks returns what is there:
```
for watermark in document.watermarks:
print(watermark.text, watermark.is_image)
```
Watermark.text is `None` for an image watermark.
Remove them all, or one at a time:
```
document.remove_watermark() # -> how many were removed
for watermark in list(document.watermarks):
watermark.remove()
```
remove_watermark() exists on Section too, for removing a single section's watermark.
# API reference
- [docx](https://toxicphreak.github.io/python-docx-ng/api/docx/index.md)
- [altchunk](https://toxicphreak.github.io/python-docx-ng/api/docx/altchunk/index.md)
- [api](https://toxicphreak.github.io/python-docx-ng/api/docx/api/index.md)
- [blkcntnr](https://toxicphreak.github.io/python-docx-ng/api/docx/blkcntnr/index.md)
- [bookmark](https://toxicphreak.github.io/python-docx-ng/api/docx/bookmark/index.md)
- [borders](https://toxicphreak.github.io/python-docx-ng/api/docx/borders/index.md)
- [caption](https://toxicphreak.github.io/python-docx-ng/api/docx/caption/index.md)
- [cleanup](https://toxicphreak.github.io/python-docx-ng/api/docx/cleanup/index.md)
- [cli](https://toxicphreak.github.io/python-docx-ng/api/docx/cli/index.md)
- [comments](https://toxicphreak.github.io/python-docx-ng/api/docx/comments/index.md)
- [copy](https://toxicphreak.github.io/python-docx-ng/api/docx/copy/index.md)
- [dml](https://toxicphreak.github.io/python-docx-ng/api/docx/dml/index.md)
- [color](https://toxicphreak.github.io/python-docx-ng/api/docx/dml/color/index.md)
- [document](https://toxicphreak.github.io/python-docx-ng/api/docx/document/index.md)
- [drawing](https://toxicphreak.github.io/python-docx-ng/api/docx/drawing/index.md)
- [enum](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/index.md)
- [base](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/base/index.md)
- [dml](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/dml/index.md)
- [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/numbering/index.md)
- [revision](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/revision/index.md)
- [section](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/section/index.md)
- [shape](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/shape/index.md)
- [style](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/style/index.md)
- [table](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/table/index.md)
- [text](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/text/index.md)
- [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/exceptions/index.md)
- [fields](https://toxicphreak.github.io/python-docx-ng/api/docx/fields/index.md)
- [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/footnotes/index.md)
- [formfield](https://toxicphreak.github.io/python-docx-ng/api/docx/formfield/index.md)
- [image](https://toxicphreak.github.io/python-docx-ng/api/docx/image/index.md)
- [bmp](https://toxicphreak.github.io/python-docx-ng/api/docx/image/bmp/index.md)
- [constants](https://toxicphreak.github.io/python-docx-ng/api/docx/image/constants/index.md)
- [emf](https://toxicphreak.github.io/python-docx-ng/api/docx/image/emf/index.md)
- [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/image/exceptions/index.md)
- [gif](https://toxicphreak.github.io/python-docx-ng/api/docx/image/gif/index.md)
- [helpers](https://toxicphreak.github.io/python-docx-ng/api/docx/image/helpers/index.md)
- [image](https://toxicphreak.github.io/python-docx-ng/api/docx/image/image/index.md)
- [jpeg](https://toxicphreak.github.io/python-docx-ng/api/docx/image/jpeg/index.md)
- [png](https://toxicphreak.github.io/python-docx-ng/api/docx/image/png/index.md)
- [svg](https://toxicphreak.github.io/python-docx-ng/api/docx/image/svg/index.md)
- [tiff](https://toxicphreak.github.io/python-docx-ng/api/docx/image/tiff/index.md)
- [webp](https://toxicphreak.github.io/python-docx-ng/api/docx/image/webp/index.md)
- [wmf](https://toxicphreak.github.io/python-docx-ng/api/docx/image/wmf/index.md)
- [math](https://toxicphreak.github.io/python-docx-ng/api/docx/math/index.md)
- [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/numbering/index.md)
- [object](https://toxicphreak.github.io/python-docx-ng/api/docx/object/index.md)
- [opc](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/index.md)
- [constants](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/constants/index.md)
- [coreprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/coreprops/index.md)
- [customprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/customprops/index.md)
- [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/exceptions/index.md)
- [extendedprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/extendedprops/index.md)
- [oxml](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/oxml/index.md)
- [package](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/package/index.md)
- [packuri](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/packuri/index.md)
- [part](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/part/index.md)
- [parts](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/index.md)
- [coreprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/coreprops/index.md)
- [custom_xml](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/custom_xml/index.md)
- [customprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/customprops/index.md)
- [extendedprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/extendedprops/index.md)
- [phys_pkg](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/phys_pkg/index.md)
- [pkgreader](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/pkgreader/index.md)
- [pkgwriter](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/pkgwriter/index.md)
- [rel](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/rel/index.md)
- [shared](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/shared/index.md)
- [spec](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/spec/index.md)
- [oxml](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/index.md)
- [bookmark](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/bookmark/index.md)
- [comments](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/comments/index.md)
- [coreprops](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/coreprops/index.md)
- [customprops](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/customprops/index.md)
- [customxml](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/customxml/index.md)
- [deletion](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/deletion/index.md)
- [document](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/document/index.md)
- [drawing](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/drawing/index.md)
- [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/exceptions/index.md)
- [extendedprops](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/extendedprops/index.md)
- [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/footnotes/index.md)
- [math](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/math/index.md)
- [ns](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/ns/index.md)
- [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/numbering/index.md)
- [object](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/object/index.md)
- [parser](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/parser/index.md)
- [revision](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/revision/index.md)
- [sdt](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/sdt/index.md)
- [section](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/section/index.md)
- [settings](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/settings/index.md)
- [shape](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/shape/index.md)
- [shared](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/shared/index.md)
- [simpletypes](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/simpletypes/index.md)
- [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/styles/index.md)
- [table](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/table/index.md)
- [text](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/index.md)
- [font](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/font/index.md)
- [form](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/form/index.md)
- [hyperlink](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/hyperlink/index.md)
- [isolate](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/isolate/index.md)
- [pagebreak](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/pagebreak/index.md)
- [paragraph](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/paragraph/index.md)
- [parfmt](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/parfmt/index.md)
- [run](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/run/index.md)
- [theme](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/theme/index.md)
- [xmlchemy](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/xmlchemy/index.md)
- [package](https://toxicphreak.github.io/python-docx-ng/api/docx/package/index.md)
- [parts](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/index.md)
- [altchunk](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/altchunk/index.md)
- [comments](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/comments/index.md)
- [document](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/document/index.md)
- [endnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/endnotes/index.md)
- [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/footnotes/index.md)
- [hdrftr](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/hdrftr/index.md)
- [image](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/image/index.md)
- [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/numbering/index.md)
- [settings](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/settings/index.md)
- [story](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/story/index.md)
- [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/styles/index.md)
- [theme](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/theme/index.md)
- [revisions](https://toxicphreak.github.io/python-docx-ng/api/docx/revisions/index.md)
- [sdt](https://toxicphreak.github.io/python-docx-ng/api/docx/sdt/index.md)
- [section](https://toxicphreak.github.io/python-docx-ng/api/docx/section/index.md)
- [settings](https://toxicphreak.github.io/python-docx-ng/api/docx/settings/index.md)
- [shape](https://toxicphreak.github.io/python-docx-ng/api/docx/shape/index.md)
- [shared](https://toxicphreak.github.io/python-docx-ng/api/docx/shared/index.md)
- [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/index.md)
- [copy](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/copy/index.md)
- [latent](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/latent/index.md)
- [style](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/style/index.md)
- [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/styles/index.md)
- [transfer](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/transfer/index.md)
- [usage](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/usage/index.md)
- [table](https://toxicphreak.github.io/python-docx-ng/api/docx/table/index.md)
- [text](https://toxicphreak.github.io/python-docx-ng/api/docx/text/index.md)
- [font](https://toxicphreak.github.io/python-docx-ng/api/docx/text/font/index.md)
- [hyperlink](https://toxicphreak.github.io/python-docx-ng/api/docx/text/hyperlink/index.md)
- [pagebreak](https://toxicphreak.github.io/python-docx-ng/api/docx/text/pagebreak/index.md)
- [paragraph](https://toxicphreak.github.io/python-docx-ng/api/docx/text/paragraph/index.md)
- [parfmt](https://toxicphreak.github.io/python-docx-ng/api/docx/text/parfmt/index.md)
- [run](https://toxicphreak.github.io/python-docx-ng/api/docx/text/run/index.md)
- [search](https://toxicphreak.github.io/python-docx-ng/api/docx/text/search/index.md)
- [tabstops](https://toxicphreak.github.io/python-docx-ng/api/docx/text/tabstops/index.md)
- [theme](https://toxicphreak.github.io/python-docx-ng/api/docx/theme/index.md)
- [types](https://toxicphreak.github.io/python-docx-ng/api/docx/types/index.md)
- [watermark](https://toxicphreak.github.io/python-docx-ng/api/docx/watermark/index.md)
## docx
Initialize `docx` package.
Export the `Document` constructor function and establish the mapping of part-type to the part-classe that implements that type.
### Document
```
Document(
docx: str | PathLike[str] | IO[bytes] | None = None,
) -> Document
```
Return a Document object loaded from `docx`, where `docx` can be either a path to a `.docx` file (a string or `os.PathLike`) or a file-like object.
Macro-enabled `.docm` files and Word templates — `.dotx` and `.dotm` — are also accepted. Their macro storage is preserved when the document is saved, but this library provides no API to read or modify it.
A template opened this way is still a template when saved; pass `as_template=False` to Document.save to write it out as an ordinary document instead.
If `docx` is missing or `None`, the built-in default document "template" is loaded.
Source code in `src/docx/api.py`
```
def Document(docx: str | os.PathLike[str] | IO[bytes] | None = None) -> DocumentObject:
"""Return a |Document| object loaded from `docx`, where `docx` can be either a path
to a ``.docx`` file (a string or ``os.PathLike``) or a file-like object.
Macro-enabled ``.docm`` files and Word templates — ``.dotx`` and ``.dotm`` — are
also accepted. Their macro storage is preserved when the document is saved, but this
library provides no API to read or modify it.
A template opened this way is still a template when saved; pass
``as_template=False`` to :meth:`.Document.save` to write it out as an ordinary
document instead.
If `docx` is missing or ``None``, the built-in default document "template" is
loaded.
"""
docx = _default_docx_path() if docx is None else docx
if isinstance(docx, os.PathLike):
docx = os.fspath(docx)
document_part = cast("DocumentPart", Package.open(docx).main_document_part)
if document_part.content_type not in _WORD_MAIN_CONTENT_TYPES:
tmpl = "file '%s' is not a Word file, content type is '%s'"
raise ValueError(tmpl % (docx, document_part.content_type))
return document_part.document
```
## altchunk
The AltChunk object, an embedded document Word imports when it opens the file.
### AltChunk
```
AltChunk(altChunk: CT_AltChunk, parent: ProvidesStoryPart)
```
Bases: `StoryChild`
Proxy for a `w:altChunk` element, an "alternative format import".
An alt-chunk holds a document in a format other than WordprocessingML — HTML, RTF, plain text, MHTML, or another .docx. Word converts it and splices the result into the document at this position when it opens the file.
The conversion is Word's, and it happens on open. This library stores and returns the embedded bytes unchanged and does not read into them, so the paragraphs and tables the alt-chunk will eventually contribute do not appear in `Document.paragraphs`, `Document.tables` or `Document.iter_inner_content()`.
Source code in `src/docx/altchunk.py`
```
def __init__(self, altChunk: CT_AltChunk, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._element = self._altChunk = altChunk
```
#### blob
```
blob: bytes
```
The bytes of the embedded document, exactly as they were provided.
#### content_type
```
content_type: str
```
The content type of the embedded document, e.g. `"text/html"`.
## api
Directly exposed API functions and classes, Document for now.
Provides a syntactically more convenient API for interacting with the OpcPackage graph.
### Document
```
Document(
docx: str | PathLike[str] | IO[bytes] | None = None,
) -> Document
```
Return a Document object loaded from `docx`, where `docx` can be either a path to a `.docx` file (a string or `os.PathLike`) or a file-like object.
Macro-enabled `.docm` files and Word templates — `.dotx` and `.dotm` — are also accepted. Their macro storage is preserved when the document is saved, but this library provides no API to read or modify it.
A template opened this way is still a template when saved; pass `as_template=False` to Document.save to write it out as an ordinary document instead.
If `docx` is missing or `None`, the built-in default document "template" is loaded.
Source code in `src/docx/api.py`
```
def Document(docx: str | os.PathLike[str] | IO[bytes] | None = None) -> DocumentObject:
"""Return a |Document| object loaded from `docx`, where `docx` can be either a path
to a ``.docx`` file (a string or ``os.PathLike``) or a file-like object.
Macro-enabled ``.docm`` files and Word templates — ``.dotx`` and ``.dotm`` — are
also accepted. Their macro storage is preserved when the document is saved, but this
library provides no API to read or modify it.
A template opened this way is still a template when saved; pass
``as_template=False`` to :meth:`.Document.save` to write it out as an ordinary
document instead.
If `docx` is missing or ``None``, the built-in default document "template" is
loaded.
"""
docx = _default_docx_path() if docx is None else docx
if isinstance(docx, os.PathLike):
docx = os.fspath(docx)
document_part = cast("DocumentPart", Package.open(docx).main_document_part)
if document_part.content_type not in _WORD_MAIN_CONTENT_TYPES:
tmpl = "file '%s' is not a Word file, content type is '%s'"
raise ValueError(tmpl % (docx, document_part.content_type))
return document_part.document
```
## blkcntnr
Block item container, used by body, cell, header, etc.
Block level items are things like paragraph and table, although there are a few other specialized ones like structured document tags.
### BlockItemContainer
```
BlockItemContainer(
element: BlockItemElement, parent: ProvidesStoryPart
)
```
Bases: `StoryChild`
Base class for proxy objects that can contain block items.
These containers include \_Body, \_Cell, header, footer, footnote, endnote, comment, and text box objects. Provides the shared functionality to add a block item like a paragraph or table.
Source code in `src/docx/blkcntnr.py`
```
def __init__(self, element: BlockItemElement, parent: t.ProvidesStoryPart):
super(BlockItemContainer, self).__init__(parent)
self._element = element
```
#### math
```
math: List[Math]
```
The equations in this container, in document order.
Includes equations inside tables in this container. See Paragraph.math for why equation text is not part of Paragraph.text.
#### content_controls
```
content_controls: list[ContentControl]
```
The structured document tags (content controls) in this container.
Nested controls are included, in document order, outermost first. The content of a control appears in `.paragraphs` and `.iter_inner_content()` as though the wrapper were not there; this is how the wrapper itself is reached.
#### paragraphs
```
paragraphs
```
A list containing the paragraphs in this container, in document order.
Includes paragraphs wrapped in a `w:sdt` (content control). Read-only.
#### tables
```
tables
```
A list containing the tables in this container, in document order.
Includes tables wrapped in a `w:sdt` (content control). Read-only.
#### add_paragraph
```
add_paragraph(
text: str = "",
style: str | ParagraphStyle | None = None,
) -> Paragraph
```
Return paragraph newly added to the end of the content in this container.
The paragraph has `text` in a single run if present, and is given paragraph style `style`.
If `style` is `None`, no paragraph style is applied, which has the same effect as applying the 'Normal' style.
Source code in `src/docx/blkcntnr.py`
```
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph:
"""Return paragraph newly added to the end of the content in this container.
The paragraph has `text` in a single run if present, and is given paragraph
style `style`.
If `style` is |None|, no paragraph style is applied, which has the same effect
as applying the 'Normal' style.
"""
paragraph = self._add_paragraph()
if text:
paragraph.add_run(text)
if style is not None:
paragraph.style = style
return paragraph
```
#### add_table
```
add_table(
rows: int,
cols: int,
width: Length,
*,
title: str | None = None,
description: str | None = None,
) -> Table
```
Return table of `width` having `rows` rows and `cols` columns.
The table is appended appended at the end of the content in this container.
`width` is evenly distributed between the table columns.
`description` is the table's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, caption-like field Word writes alongside it. Both are omitted from the XML when `None`, and are equivalent to assigning `Table.title` and `Table.description` after the fact.
Source code in `src/docx/blkcntnr.py`
```
def add_table(
self,
rows: int,
cols: int,
width: Length,
*,
title: str | None = None,
description: str | None = None,
) -> Table:
"""Return table of `width` having `rows` rows and `cols` columns.
The table is appended appended at the end of the content in this container.
`width` is evenly distributed between the table columns.
`description` is the table's alternative text, which is what a screen reader
announces and what an accessibility check looks for. `title` is the separate,
caption-like field Word writes alongside it. Both are omitted from the XML when
|None|, and are equivalent to assigning `Table.title` and `Table.description`
after the fact.
"""
from docx.table import Table
tbl = CT_Tbl.new_tbl(rows, cols, width)
self._element._insert_tbl(tbl) # pyright: ignore[reportPrivateUsage]
table = Table(tbl, self)
if title is not None:
table.title = title
if description is not None:
table.description = description
return table
```
#### iter_inner_content
```
iter_inner_content() -> Iterator[Paragraph | Table]
```
Generate each `Paragraph` or `Table` in this container in document order.
Source code in `src/docx/blkcntnr.py`
```
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each `Paragraph` or `Table` in this container in document order."""
from docx.table import Table
for element in self._element.inner_content_elements:
yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self))
```
#### iter_paragraphs
```
iter_paragraphs(tables: bool = True) -> Iterator[Paragraph]
```
Generate every Paragraph in this container, in document order.
Unlike paragraphs, this descends into tables when `tables` is `True`, including tables nested inside other tables, so it reaches every paragraph in the container rather than only the top-level ones.
Source code in `src/docx/blkcntnr.py`
```
def iter_paragraphs(self, tables: bool = True) -> Iterator[Paragraph]:
"""Generate every |Paragraph| in this container, in document order.
Unlike :attr:`paragraphs`, this descends into tables when `tables` is |True|,
including tables nested inside other tables, so it reaches every paragraph in
the container rather than only the top-level ones.
"""
for item in self.iter_inner_content():
if isinstance(item, Paragraph):
yield item
elif tables:
for row in item.rows:
for cell in row.cells:
yield from cell.iter_paragraphs(tables=True)
```
#### replace_text
```
replace_text(
old: str,
new: str,
*,
count: int = -1,
regex: bool = False,
flags: int = 0,
tables: bool = True,
) -> int
```
Replace occurrences of `old` with `new` in this container; return how many.
Each paragraph is replaced in as described by Paragraph.replace_text, which is where the details of matching and formatting are documented. Tables are included unless `tables` is `False`; `count` of -1 replaces every match and any other value is a limit on the total across the whole container.
Source code in `src/docx/blkcntnr.py`
```
def replace_text(
self,
old: str,
new: str,
*,
count: int = -1,
regex: bool = False,
flags: int = 0,
tables: bool = True,
) -> int:
"""Replace occurrences of `old` with `new` in this container; return how many.
Each paragraph is replaced in as described by :meth:`.Paragraph.replace_text`,
which is where the details of matching and formatting are documented. Tables are
included unless `tables` is |False|; `count` of -1 replaces every match and any
other value is a limit on the total across the whole container.
"""
from docx.text.search import compile_pattern, replace_in_paragraph
if count == 0:
return 0
pattern = compile_pattern(old, regex, flags)
replaced = 0
for paragraph in self.iter_paragraphs(tables=tables):
remaining = -1 if count < 0 else count - replaced
replaced += replace_in_paragraph(
paragraph._p, # pyright: ignore[reportPrivateUsage]
pattern,
new,
remaining,
regex,
)
if count >= 0 and replaced >= count:
break
return replaced
```
## bookmark
The Bookmark and Bookmarks proxy objects.
A bookmark names a range of a document. It is the anchor mechanism everything that refers to a place in a document is built on: internal hyperlinks, cross-references, captions that renumber, and table-of-contents entries.
### Bookmark
```
Bookmark(
bookmarkStart: CT_BookmarkStart,
parent: ProvidesStoryPart,
)
```
Bases: `Parented`
Proxy for a `w:bookmarkStart` element and the range it names.
Source code in `src/docx/bookmark.py`
```
def __init__(self, bookmarkStart: CT_BookmarkStart, parent: t.ProvidesStoryPart):
super(Bookmark, self).__init__(parent)
self._element = self._bookmarkStart = bookmarkStart
```
#### id
```
id: int
```
The `w:id` pairing this bookmark's two delimiters.
Unique across the document.
#### is_closed
```
is_closed: bool
```
`True` when this bookmark has a matching `w:bookmarkEnd`.
An unmatched start is invalid but appears in real documents, so it is reported rather than raised on. The `.text` of an unclosed bookmark is the empty string.
#### is_hidden
```
is_hidden: bool
```
`True` for a bookmark Word maintains for itself.
`_GoBack` records the last edit position and `_Toc…` anchors a table-of-contents entry. Bookmarks leaves these out by default.
#### name
```
name: str
```
The name of this bookmark, as shown in Word's bookmark dialog.
#### text
```
text: str
```
The text of the content this bookmark spans.
Paragraph boundaries inside the range become newlines, as they do for a table cell. The empty string when the bookmark is unclosed or spans no text.
#### delete
```
delete() -> None
```
Remove this bookmark, leaving the content it named in place.
Removes both delimiters, including an unmatched one.
Source code in `src/docx/bookmark.py`
```
def delete(self) -> None:
"""Remove this bookmark, leaving the content it named in place.
Removes both delimiters, including an unmatched one.
"""
bookmarkEnd = self._bookmarkStart.bookmarkEnd
if bookmarkEnd is not None:
bookmarkEnd.getparent().remove(bookmarkEnd)
self._bookmarkStart.getparent().remove(self._bookmarkStart)
```
### Bookmarks
```
Bookmarks(
element: ProvidesXmlPart, parent: ProvidesStoryPart
)
```
Bases: `Parented`, `Sequence[Bookmark]`
The bookmarks in a document, in document order.
Supports `len()`, iteration, indexed access and lookup by name:
```
document.bookmarks["Introduction"].text
```
Bookmarks Word maintains for itself, such as `_GoBack` and the `_Toc…` anchors, are left out; pass `include_hidden=True` to iter_all to see them.
Source code in `src/docx/bookmark.py`
```
def __init__(self, element: t.ProvidesXmlPart, parent: t.ProvidesStoryPart):
super(Bookmarks, self).__init__(parent)
self._element = element
```
#### get
```
get(
name: str, default: Bookmark | None = None
) -> Bookmark | None
```
The bookmark named `name`, or `default` when there is none.
Source code in `src/docx/bookmark.py`
```
def get(self, name: str, default: Bookmark | None = None) -> Bookmark | None:
"""The bookmark named `name`, or `default` when there is none."""
try:
return self[name]
except KeyError:
return default
```
#### iter_all
```
iter_all(include_hidden: bool = True) -> Iterator[Bookmark]
```
Generate every bookmark in the document, in document order.
Includes Word's own bookmarks unless `include_hidden` is `False`, and includes a bookmark whose `w:bookmarkEnd` is missing.
Source code in `src/docx/bookmark.py`
```
def iter_all(self, include_hidden: bool = True) -> Iterator[Bookmark]:
"""Generate every bookmark in the document, in document order.
Includes Word's own bookmarks unless `include_hidden` is |False|, and includes a
bookmark whose `w:bookmarkEnd` is missing.
"""
for bookmarkStart in self._element.xpath("//w:bookmarkStart"):
bookmark = Bookmark(bookmarkStart, self._parent)
if include_hidden or not bookmark.is_hidden:
yield bookmark
```
## borders
The border-edge proxy objects shared by tables, cells, paragraphs and pages.
Four containers in WordprocessingML carry a set of border edges — `w:tblBorders`, `w:tcBorders`, `w:pBdr` and `w:pgBorders` — and they differ only in which edges they admit and where the element lives. The mapping proxy and the per-edge proxy are defined here once so all four spell the same; each container supplies its own small subclass naming its edges and saying how to reach its element.
### \_Border
```
_Border(borders: _Borders, edge: str)
```
One border edge of a table, cell, paragraph or page, e.g. `table.borders["top"]`.
A border edge that is not set has `None` for every property, meaning the effective appearance of that edge is inherited from the style hierarchy. Assigning to any property other than line on an edge that is not set creates it with a line style of `WD_LINE_STYLE.SINGLE`, because a border with no line style is not valid XML. Assigning `None` to line removes the edge entirely.
Source code in `src/docx/borders.py`
```
def __init__(self, borders: _Borders, edge: str):
self._borders = borders
self._edge = edge
```
#### color
```
color: RGBColor | None
```
RGBColor of this border edge, or `None` when it has no explicit color.
As for ColorFormat, a border whose color is the automatic color reads as `None`; Word chooses that color at render time, so there is no RGB value to report.
#### line
```
line: WD_LINE_STYLE | None
```
Member of WdLineStyle, or `None` when this edge is not set.
#### size
```
size: Length | None
```
Width of this border line, or `None` when it has no explicit width.
The underlying `w:sz` attribute counts eighths of a point, so an assigned value is rounded to the nearest eighth of a point.
#### space
```
space: Length | None
```
Offset of this border from the content it surrounds, or `None` when not set.
The underlying `w:space` attribute counts whole points, so an assigned value is rounded to the nearest point.
### \_Borders
```
_Borders(edges: tuple[str, ...])
```
Bases: `Mapping[str, _Border]`
The border edges of a table, cell, paragraph or page, keyed by edge name.
A read-only mapping in the sense that the set of keys is fixed; the \_Border object each key maps to is what you assign through:
```
table.borders["top"].line = WD_LINE_STYLE.SINGLE
```
Every edge admitted by the schema is always a key, whether or not it is set, so iterating yields edges with a \_Border.line of `None` as well.
Edge names are the local names used in the XML. A table admits `top`, `start`, `left`, `bottom`, `end`, `right`, `insideH` and `insideV`; a cell adds `tl2br` and `tr2bl`; a paragraph has `top`, `left`, `bottom`, `right`, `between` and `bar`; a page has the plain four. Word writes `left` and `right` for a left-to-right table and `start` and `end` for a right-to-left one.
Source code in `src/docx/borders.py`
```
def __init__(self, edges: tuple[str, ...]):
self._edges = edges
```
#### clear
```
clear() -> None
```
Remove every border edge, restoring inheritance from the style hierarchy.
Source code in `src/docx/borders.py`
```
@abstractmethod
def clear(self) -> None:
"""Remove every border edge, restoring inheritance from the style hierarchy."""
```
## caption
Captions — a label, a self-renumbering sequence field, and a cross-reference target.
Everything a caption is built from already existed: `Paragraph.add_field()`, the `SEQ` builder in `docx.fields`, `REF` for the reference, and bookmarks. What was missing was the one call that puts them together, because assembling one by hand means:
1. insert a paragraph in the "Caption" style
1. add the literal label text and separator
1. add a `SEQ Figure \* ARABIC` field
1. wrap the whole thing in a bookmark with a `_Ref`-prefixed name and an unused id
1. remember that name so a later `REF` field can point at it
Step 4 is the one people get wrong. Word's own cross-reference dialogue offers only targets whose bookmark name follows the `_Ref` convention, so a caption bookmarked with an arbitrary name is invisible in it — the caption works, and the user cannot reference it from the UI.
### Caption
```
Caption(
paragraph: Paragraph, bookmark_name: str, label: str
)
```
A caption paragraph, carrying the bookmark name a cross-reference points at.
Returned by Document.add_caption. It is a thin wrapper over the paragraph; paragraph is the Paragraph itself for any further formatting.
Source code in `src/docx/caption.py`
```
def __init__(self, paragraph: Paragraph, bookmark_name: str, label: str):
self._paragraph = paragraph
self._bookmark_name = bookmark_name
self._label = label
```
#### paragraph
```
paragraph: Paragraph
```
The Paragraph this caption is.
#### bookmark_name
```
bookmark_name: str
```
The bookmark name a cross-reference to this caption uses:
```
document.add_paragraph().add_field(
fields.cross_reference(caption.bookmark_name)
)
```
Generated in Word's own `_Ref` shape, so Word's cross-reference dialogue offers this caption as a target. That shape also means the bookmark does not appear in Document.bookmarks, which leaves out the ones Word maintains for itself; Bookmarks.iter_all reaches it.
#### label
```
label: str
```
The caption's series, e.g. `"Figure"`. Word numbers each independently.
#### text
```
text: str
```
The caption's text as the document currently reads it.
The number is a `SEQ` field, so it shows only once Word has computed it; before that this reads as the label and the caption text with the number missing.
#### number
```
number: str | None
```
The number Word last computed for this caption, or `None`.
A `SEQ` field's result is cached in the document, so this reads back after a round trip through Word. It is `None` for a caption this library has just written, which has no cached result yet.
### next_ref_bookmark_name
```
next_ref_bookmark_name(document_element: object) -> str
```
A `_Ref`-prefixed bookmark name unused in the document.
Word derives its own from a timestamp; a counter is used here instead, since a timestamp would make the same document generate different bytes on each run and byte-reproducible output is something this library keeps.
Source code in `src/docx/caption.py`
```
def next_ref_bookmark_name(document_element: object) -> str:
"""A `_Ref`-prefixed bookmark name unused in the document.
Word derives its own from a timestamp; a counter is used here instead, since a
timestamp would make the same document generate different bytes on each run and
byte-reproducible output is something this library keeps.
"""
highest = 0
for name in document_element.xpath( # pyright: ignore[reportAttributeAccessIssue]
"//w:bookmarkStart/@w:name"
):
match = _REF_NAME_RE.match(name)
if match:
highest = max(highest, int(match.group(1)))
return "%s%0*d" % (_REF_PREFIX, _REF_NAME_DIGITS, highest + 1)
```
### add_caption
```
add_caption(
container: object,
label: str,
text: str = "",
*,
style: str | None = "Caption",
separator: str = " ",
restart_at_heading_level: int | None = None,
before: Paragraph | None = None,
) -> Caption
```
Build a caption paragraph in `container`; see Document.add_caption.
Source code in `src/docx/caption.py`
```
def add_caption(
container: object,
label: str,
text: str = "",
*,
style: str | None = "Caption",
separator: str = " ",
restart_at_heading_level: int | None = None,
before: Paragraph | None = None,
) -> Caption:
"""Build a caption paragraph in `container`; see :meth:`.Document.add_caption`."""
from docx.text.paragraph import Paragraph
paragraph = container.add_paragraph() # pyright: ignore[reportAttributeAccessIssue]
if before is not None:
before._p.addprevious(paragraph._p) # pyright: ignore[reportPrivateUsage]
if style is not None:
paragraph.style = style
paragraph.add_run(label + " ")
paragraph.add_field(
fields.sequence(label, restart_at_heading_level=restart_at_heading_level)
)
if text:
paragraph.add_run(separator + text)
part = paragraph.part
bookmark_name = next_ref_bookmark_name(part.element)
paragraph._p.add_bookmark_around_content( # pyright: ignore[reportPrivateUsage]
part.next_bookmark_id, bookmark_name
)
assert isinstance(paragraph, Paragraph)
return Caption(paragraph, bookmark_name, label)
```
### caption_bookmark_names
```
caption_bookmark_names(
document_element: object,
) -> tuple[str, ...]
```
Every `_Ref`-prefixed bookmark name in the document, in document order.
Source code in `src/docx/caption.py`
```
def caption_bookmark_names(document_element: object) -> tuple[str, ...]:
"""Every `_Ref`-prefixed bookmark name in the document, in document order."""
return tuple(
name
for name in document_element.xpath( # pyright: ignore[reportAttributeAccessIssue]
"//w:bookmarkStart/@w:name"
)
if _REF_NAME_RE.match(name)
)
```
## cleanup
Removing the things a document carries that nothing points at.
Three separate kinds of dead weight, each reached the same way — find what is referenced, drop the rest:
- **Styles.** A document created by this library defines 164 of them and a one-paragraph document references one. This is Styles.remove_unused, built on the reachability closure in `docx.styles.usage`.
- **Numbering definitions.** A `w:abstractNum` no `w:num` points at, and a `w:num` no `w:numPr` points at, are both dead.
- **Orphan media.** An image part related from nothing, left behind when the run or shape that displayed it was deleted. The `.delete()` API added in 2.0.0 makes this reachable from ordinary use.
cleanup runs the lot; the individual operations stay public, because "remove unused styles but leave my media alone" is a reasonable thing to want.
### CleanupResult
Bases: `NamedTuple`
What a cleanup pass removed.
### remove_unused_numbering
```
remove_unused_numbering(
document_part: DocumentPart,
) -> tuple[Tuple[int, ...], Tuple[int, ...]]
```
Drop the numbering definitions nothing references; return (numIds, abstractNumIds).
A `w:num` is dead when no `w:numPr` in any story part or style names its numId. A `w:abstractNum` is dead when no surviving `w:num` points at it *and* no surviving abstract definition chains to it through `w:numStyleLink`.
Does nothing, and reports nothing removed, for a document with no numbering part — reading one would create it.
Source code in `src/docx/cleanup.py`
```
def remove_unused_numbering(document_part: DocumentPart) -> tuple[Tuple[int, ...], Tuple[int, ...]]:
"""Drop the numbering definitions nothing references; return (numIds, abstractNumIds).
A `w:num` is dead when no `w:numPr` in any story part or style names its numId. A
`w:abstractNum` is dead when no surviving `w:num` points at it *and* no surviving
abstract definition chains to it through `w:numStyleLink`.
Does nothing, and reports nothing removed, for a document with no numbering part —
reading one would create it.
"""
if not document_part.has_numbering_part:
return (), ()
numbering = document_part.numbering_part.element
referenced = _referenced_num_ids(document_part)
removed_num_ids: list[int] = []
for num in list(numbering.num_lst):
if num.numId not in referenced:
removed_num_ids.append(num.numId)
numbering.remove(num)
live_abstract_ids = {num.abstractNumId.val for num in numbering.num_lst}
# -- an abstract definition can point at another through `w:numStyleLink`, so the
# -- survivors' own references have to be followed before deciding what is dead --
by_id = {a.abstractNumId: a for a in numbering.abstractNum_lst}
pending = set(live_abstract_ids)
reachable: Set[int] = set()
while pending:
abstract_id = pending.pop()
if abstract_id in reachable or abstract_id not in by_id:
continue
reachable.add(abstract_id)
for style_id in by_id[abstract_id].xpath("./w:numStyleLink/@w:val"):
for other in numbering.abstractNum_lst:
if other.xpath("./w:styleLink/@w:val") == [style_id]:
pending.add(other.abstractNumId)
removed_abstract_ids: list[int] = []
for abstractNum in list(numbering.abstractNum_lst):
if abstractNum.abstractNumId not in reachable:
removed_abstract_ids.append(abstractNum.abstractNumId)
numbering.remove(abstractNum)
return tuple(removed_num_ids), tuple(removed_abstract_ids)
```
### remove_orphan_media
```
remove_orphan_media(
document_part: DocumentPart,
) -> Tuple[str, ...]
```
Drop image relationships nothing in the document part's XML refers to.
An `r:embed`, `r:link` or `r:id` naming the relationship is what keeps an image alive. Deleting a paragraph that held a picture leaves the relationship and the part behind; this is what removes them.
Only the document part's own image relationships are considered. A picture in a header or a footnote belongs to that part's relationships and is not this pass's business — a header image is not orphaned by anything happening in the body.
Source code in `src/docx/cleanup.py`
```
def remove_orphan_media(document_part: DocumentPart) -> Tuple[str, ...]:
"""Drop image relationships nothing in the document part's XML refers to.
An `r:embed`, `r:link` or `r:id` naming the relationship is what keeps an image
alive. Deleting a paragraph that held a picture leaves the relationship and the part
behind; this is what removes them.
Only the document part's own image relationships are considered. A picture in a
header or a footnote belongs to that part's relationships and is not this pass's
business — a header image is not orphaned by anything happening in the body.
"""
referenced = set(
document_part.element.xpath("//@r:embed | //@r:link | //@r:id")
)
removed: list[str] = []
for rId, rel in list(document_part.rels.items()):
if rel.reltype != RT.IMAGE or rel.is_external:
continue
if rId not in referenced:
removed.append(str(rel.target_part.partname))
document_part.drop_rel(rId)
return tuple(removed)
```
### cleanup
```
cleanup(
document_part: DocumentPart,
*,
styles: bool = True,
numbering: bool = True,
media: bool = True,
latent_styles: bool = False,
keep: tuple[str, ...] = (),
) -> CleanupResult
```
Remove what this document carries that nothing points at.
Each flag turns one pass on or off; `keep` names styles to preserve along with their dependencies, as for Styles.remove_unused.
`latent_styles` is `False` by default and separate from `styles` on purpose: removing a `w:lsdException` changes what a user sees in Word's style gallery rather than how the document renders, which is a different kind of change from removing a style definition.
Styles are pruned before numbering, so a numbering definition kept alive only by a style that is about to go is correctly seen as dead.
Source code in `src/docx/cleanup.py`
```
def cleanup(
document_part: DocumentPart,
*,
styles: bool = True,
numbering: bool = True,
media: bool = True,
latent_styles: bool = False,
keep: tuple[str, ...] = (),
) -> CleanupResult:
"""Remove what this document carries that nothing points at.
Each flag turns one pass on or off; `keep` names styles to preserve along with their
dependencies, as for :meth:`.Styles.remove_unused`.
`latent_styles` is |False| by default and separate from `styles` on purpose:
removing a `w:lsdException` changes what a user sees in Word's style gallery rather
than how the document renders, which is a different kind of change from removing a
style definition.
Styles are pruned before numbering, so a numbering definition kept alive only by a
style that is about to go is correctly seen as dead.
"""
removed_styles: Tuple[str, ...] = ()
if styles:
removed_styles = document_part.styles.remove_unused(keep=keep)
removed_nums: Tuple[int, ...] = ()
removed_abstract_nums: Tuple[int, ...] = ()
if numbering:
removed_nums, removed_abstract_nums = remove_unused_numbering(document_part)
removed_media: Tuple[str, ...] = ()
if media:
removed_media = remove_orphan_media(document_part)
trimmed = 0
if latent_styles:
trimmed = document_part.styles.latent_styles.trim()
return CleanupResult(
styles=removed_styles,
num_ids=removed_nums,
abstract_num_ids=removed_abstract_nums,
media=removed_media,
latent_styles=trimmed,
)
```
## cli
A command-line front end for the inspection and cleanup operations.
`python -m docx` answers the questions people ask about a `.docx` one at a time: what styles it defines, which are actually used, and why the file is 900 KB. Those are diagnostic operations — you run them to find out something about a file, not as part of an application — and a diagnostic API with no command-line front end mostly does not get used.
Deliberately thin: every subcommand maps onto one public library operation and holds no logic of its own, so the CLI cannot drift from the API or grow behaviour that is only reachable through it. argparse only; no new runtime dependency.
Two rules the commands keep:
- **Never modify the input.** `cleanup` writes to `-o` and refuses without it. Someone will point it at their only copy.
- **Exit codes matter**, because this ends up in scripts: non-zero for a document that cannot be opened, and `--check` reports what would be removed and exits non-zero if anything would be, so it can be a CI gate.
### main
```
main(
argv: Sequence[str] | None = None,
stdout: IO[str] | None = None,
) -> int
```
Run the CLI; return the process exit code.
`argv` and `stdout` are injectable so the tests do not have to drive a subprocess.
Source code in `src/docx/cli.py`
```
def main(argv: Sequence[str] | None = None, stdout: IO[str] | None = None) -> int:
"""Run the CLI; return the process exit code.
`argv` and `stdout` are injectable so the tests do not have to drive a subprocess.
"""
out = sys.stdout if stdout is None else stdout
parser = _build_parser()
args = parser.parse_args(argv)
if getattr(args, "func", None) is None:
parser.print_help(out)
return 0
try:
return args.func(args, out)
except FileNotFoundError as e:
print("error: no such file: %s" % e.filename, file=sys.stderr)
return EXIT_CANNOT_OPEN
except EncryptedPackageError:
print("error: document is password-protected", file=sys.stderr)
return EXIT_CANNOT_OPEN
except (PackageNotFoundError, zipfile.BadZipFile) as e:
print("error: cannot open document: %s" % e, file=sys.stderr)
return EXIT_CANNOT_OPEN
except KeyError as e:
# -- `styles extract --names` and `cleanup --keep` name styles, and a name the
# -- document does not define surfaces here rather than as a traceback --
message = e.args[0] if e.args else str(e)
print("error: %s" % message, file=sys.stderr)
return EXIT_CANNOT_OPEN
```
## comments
Collection providing access to comments added to this document.
### Comments
```
Comments(
comments_elm: CT_Comments, comments_part: CommentsPart
)
```
Collection containing the comments added to this document.
Source code in `src/docx/comments.py`
```
def __init__(self, comments_elm: CT_Comments, comments_part: CommentsPart):
self._comments_elm = comments_elm
self._comments_part = comments_part
```
#### add_comment
```
add_comment(
text: str = "",
author: str = "",
initials: str | None = "",
) -> Comment
```
Add a new comment to the document and return it.
The comment is added to the end of the comments collection and is assigned a unique comment-id.
If `text` is provided, it is added to the comment. This option provides for the common case where a comment contains a modest passage of plain text. Multiple paragraphs can be added using the `text` argument by separating their text with newlines (`"\\n"`). Between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`.
The default is to place a single empty paragraph in the comment, which is the same behavior as the Word UI when you add a comment. New runs can be added to the first paragraph in the empty comment with `comments.paragraphs[0].add_run()` to adding more complex text with emphasis or images. Additional paragraphs can be added using `.add_paragraph()`.
`author` is a required attribute, set to the empty string by default.
`initials` is an optional attribute, set to the empty string by default. Passing `None` for the `initials` parameter causes that attribute to be omitted from the XML.
Source code in `src/docx/comments.py`
```
def add_comment(self, text: str = "", author: str = "", initials: str | None = "") -> Comment:
"""Add a new comment to the document and return it.
The comment is added to the end of the comments collection and is assigned a unique
comment-id.
If `text` is provided, it is added to the comment. This option provides for the common
case where a comment contains a modest passage of plain text. Multiple paragraphs can be
added using the `text` argument by separating their text with newlines (`"\\\\n"`).
Between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`.
The default is to place a single empty paragraph in the comment, which is the same
behavior as the Word UI when you add a comment. New runs can be added to the first
paragraph in the empty comment with `comments.paragraphs[0].add_run()` to adding more
complex text with emphasis or images. Additional paragraphs can be added using
`.add_paragraph()`.
`author` is a required attribute, set to the empty string by default.
`initials` is an optional attribute, set to the empty string by default. Passing |None|
for the `initials` parameter causes that attribute to be omitted from the XML.
"""
comment_elm = self._comments_elm.add_comment()
comment_elm.author = author
comment_elm.initials = initials
comment_elm.date = dt.datetime.now(dt.timezone.utc)
comment = Comment(comment_elm, self._comments_part)
if text == "":
return comment
para_text_iter = iter(text.split("\n"))
first_para_text = next(para_text_iter)
first_para = comment.paragraphs[0]
first_para.add_run(first_para_text)
for s in para_text_iter:
comment.add_paragraph(text=s)
return comment
```
#### get
```
get(comment_id: int) -> Comment | None
```
Return the comment identified by `comment_id`, or `None` if not found.
Source code in `src/docx/comments.py`
```
def get(self, comment_id: int) -> Comment | None:
"""Return the comment identified by `comment_id`, or |None| if not found."""
comment_elm = self._comments_elm.get_comment_by_id(comment_id)
return Comment(comment_elm, self._comments_part) if comment_elm is not None else None
```
### Comment
```
Comment(
comment_elm: CT_Comment, comments_part: CommentsPart
)
```
Bases: `BlockItemContainer`
Proxy for a single comment in the document.
Provides methods to access comment metadata such as author, initials, and date.
A comment is also a block-item container, similar to a table cell, so it can contain both paragraphs and tables and its paragraphs can contain rich text, hyperlinks and images, although the common case is that a comment contains a single paragraph of plain text like a sentence or phrase.
Note that certain content like tables may not be displayed in the Word comment sidebar due to space limitations. Such "over-sized" content can still be viewed in the review pane.
Source code in `src/docx/comments.py`
```
def __init__(self, comment_elm: CT_Comment, comments_part: CommentsPart):
super().__init__(comment_elm, comments_part)
self._comment_elm = comment_elm
```
#### author
```
author: str
```
Read/write. The recorded author of this comment.
This field is required but can be set to the empty string.
#### comment_id
```
comment_id: int
```
The unique identifier of this comment.
#### initials
```
initials: str | None
```
Read/write. The recorded initials of the comment author.
This attribute is optional in the XML, returns `None` if not set. Assigning `None` removes any existing initials from the XML.
#### text
```
text: str
```
The text content of this comment as a string.
Only content in paragraphs is included and of course all emphasis and styling is stripped.
Paragraph boundaries are indicated with a newline (`"\\n"`)
#### timestamp
```
timestamp: datetime | None
```
The date and time this comment was authored.
This attribute is optional in the XML, returns `None` if not set.
#### add_paragraph
```
add_paragraph(
text: str = "",
style: str | ParagraphStyle | None = None,
) -> Paragraph
```
Return paragraph newly added to the end of the content in this container.
The paragraph has `text` in a single run if present, and is given paragraph style `style`. When `style` is `None` or ommitted, the "CommentText" paragraph style is applied, which is the default style for comments.
Source code in `src/docx/comments.py`
```
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph:
"""Return paragraph newly added to the end of the content in this container.
The paragraph has `text` in a single run if present, and is given paragraph style `style`.
When `style` is |None| or ommitted, the "CommentText" paragraph style is applied, which is
the default style for comments.
"""
paragraph = super().add_paragraph(text, style)
# -- have to assign style directly to element because `paragraph.style` raises when
# -- a style is not present in the styles part
if style is None:
paragraph._p.style = "CommentText" # pyright: ignore[reportPrivateUsage]
return paragraph
```
## copy
Duplicating content — a paragraph, a run, a table row, a whole table.
`copy.deepcopy(paragraph._p)` followed by an `addnext()` works for plain text and quietly breaks for anything interesting. Everything this module does beyond the deep copy is a repair of one of those breakages:
- a picture's `r:embed` names a relationship id belonging to the *source* part, so a copied image is either the wrong image or a dangling reference;
- a hyperlink's `r:id` has the same problem, and points at an external target that may not exist in the destination package;
- `wp:docPr/@id` must be unique document-wide, and a deep copy duplicates it;
- bookmark ids and names collide, and Word treats a duplicate bookmark name as a second bookmark competing for anything that refers to it;
- a copy into a *different* document carries a `w:pStyle` naming a style that may not be there, and a `w:numPr` naming a `numId` that certainly is not.
The two hard pieces already existed: `Styles.copy_style_from()` resolves the style closure and carries numbering across, and `StoryPart.next_id` allocates non-colliding drawing ids. This is what uses them.
### copy_content
```
copy_content(
element: BaseOxmlElement,
source_part: StoryPart,
dest_part: StoryPart,
*,
missing_style: str = "copy",
) -> BaseOxmlElement
```
A deep copy of `element` fit to be inserted into `dest_part`.
The copy is not attached to anything; the caller places it. See Paragraph.copy_to for what `missing_style` means.
Source code in `src/docx/copy.py`
```
def copy_content(
element: BaseOxmlElement,
source_part: StoryPart,
dest_part: StoryPart,
*,
missing_style: str = "copy",
) -> BaseOxmlElement:
"""A deep copy of `element` fit to be inserted into `dest_part`.
The copy is not attached to anything; the caller places it. See
:meth:`.Paragraph.copy_to` for what `missing_style` means.
"""
if missing_style not in _MISSING_STYLE_POLICIES:
raise ValueError(
"missing_style must be one of %s, got %r"
% (", ".join(repr(p) for p in _MISSING_STYLE_POLICIES), missing_style)
)
new_element = copymod.deepcopy(element)
_remap_relationships(new_element, source_part, dest_part)
_reassign_drawing_ids(new_element, dest_part)
_strip_bookmarks(new_element)
if source_part is not dest_part:
_carry_styles(new_element, source_part, dest_part, missing_style)
_carry_numbering(new_element, source_part, dest_part)
return new_element
```
### destination_for
```
destination_for(
container: object,
) -> tuple[StoryPart, BaseOxmlElement]
```
The `(part, element)` a copy goes into for `container`.
A Document is not itself a block-item container — its body is — so it is unwrapped here rather than at each call site.
Source code in `src/docx/copy.py`
```
def destination_for(container: object) -> tuple[StoryPart, BaseOxmlElement]:
"""The `(part, element)` a copy goes into for `container`.
A |Document| is not itself a block-item container — its body is — so it is
unwrapped here rather than at each call site.
"""
from docx.document import Document
if isinstance(container, Document):
body = container._body # pyright: ignore[reportPrivateUsage]
return container.part, body._element # pyright: ignore[reportPrivateUsage]
return (
container.part, # pyright: ignore[reportAttributeAccessIssue]
container._element, # pyright: ignore[reportAttributeAccessIssue,reportPrivateUsage]
)
```
### place
```
place(
new_element: BaseOxmlElement,
dest_element: BaseOxmlElement,
before: object = None,
after: object = None,
) -> None
```
Insert `new_element` into `dest_element`, relative to `before` or `after`.
With neither, the copy is appended. A body ending in a `w:sectPr` appends before it, since the section properties must stay last.
Source code in `src/docx/copy.py`
```
def place(
new_element: BaseOxmlElement,
dest_element: BaseOxmlElement,
before: object = None,
after: object = None,
) -> None:
"""Insert `new_element` into `dest_element`, relative to `before` or `after`.
With neither, the copy is appended. A body ending in a `w:sectPr` appends before it,
since the section properties must stay last.
"""
if before is not None and after is not None:
raise ValueError("pass at most one of `before` and `after`")
if before is not None:
_element_of(before).addprevious(new_element)
return
if after is not None:
_element_of(after).addnext(new_element)
return
sectPr = dest_element.find(qn("w:sectPr"))
if sectPr is not None:
sectPr.addprevious(new_element)
else:
dest_element.append(new_element)
```
## document
Document and closely related objects.
### Document
```
Document(element: CT_Document, part: DocumentPart)
```
Bases: `ElementProxy`
WordprocessingML (WML) document.
Not intended to be constructed directly. Use `docx.Document` to open or create a document.
Source code in `src/docx/document.py`
```
def __init__(self, element: CT_Document, part: DocumentPart):
super(Document, self).__init__(element)
self._element = element
self._part = part
self.__body = None
```
#### alt_chunks
```
alt_chunks: List[AltChunk]
```
The AltChunk objects in the document body, in document order.
Only alt-chunks that are direct children of the body appear here; the schema also allows one inside a table cell or other block container.
#### comments
```
comments: Comments
```
A Comments object providing access to comments added to the document.
#### custom_properties
```
custom_properties: CustomProperties
```
A CustomProperties object providing the arbitrary named values attached to this document.
Behaves as a mutable mapping of name to value. The part holding them is created the first time this is used, so a document that never touches it gains no `/docProps/custom.xml`.
#### core_properties
```
core_properties
```
A CoreProperties object providing Dublin Core properties of document.
#### extended_properties
```
extended_properties
```
An ExtendedProperties object providing the application-specific properties of the document, such as word count and producing application.
#### footnotes
```
footnotes: Footnotes
```
A Footnotes object providing access to the footnotes of this document.
The footnotes part is created the first time this is used, so a document that never touches it gains no `/word/footnotes.xml`.
#### custom_xml_parts
```
custom_xml_parts: Tuple[CustomXmlPart, ...]
```
The custom XML data store items of this document, in relationship order.
Each part offers `.item_id`, `.schema_refs`, `.element` and `.xml`. The item content is arbitrary caller-supplied XML, so `.element` is a plain parsed tree with no element classes of its own.
#### has_macros
```
has_macros: bool
```
`True` when this document carries a VBA project.
The cheap predicate; vba_project is what reads the bytes.
#### vba_project
```
vba_project: bytes | None
```
The macro project of this document as bytes, or `None` when it has none.
A `.docm` or `.dotm` carries its macros in `word/vbaProject.bin`, an OLE compound file. This library does not parse it, but it round-trips untouched, so the two operations people actually want are expressible:
**Strip the macros** from a document received from elsewhere:
```
del document.vba_project
document.save("clean.docx")
```
**Transplant a project** authored in Word into a generated document:
```
document.vba_project = donor.vba_project
```
Assigning switches the main part to the macro-enabled content type, and removing switches it back. Word silently ignores macros in a document whose main part does not claim to be macro-enabled, and warns the user about macros in one that claims to be but is not, so the two are kept in step rather than left to the caller.
Note this sets the content type; it does not choose the file extension for you. A macro-enabled document conventionally has a `.docm` extension.
#### endnotes
```
endnotes: Endnotes
```
An Endnotes object providing access to the endnotes of this document.
The endnotes part is created the first time this is used, so a document that never touches it gains no `/word/endnotes.xml`.
#### fields
```
fields: List[Field]
```
A Field for each field in the document body, in document order.
Outermost first, so a `PAGEREF` nested in a table-of-contents entry follows the `TOC` field containing it. Fields in a header or footer are not in the document part and so are not included; reach those through the paragraphs of the header or footer.
#### form_fields
```
form_fields: List[FormField]
```
A FormField instance for each legacy form field in the document body.
Fields appear in document order, including those inside tables. Fields in a header or footer are not in the document part and so are not included; reach those through the paragraphs of the header or footer.
#### floating_shapes
```
floating_shapes
```
The FloatingShapes collection for this document.
A floating shape is anchored rather than inline: it is positioned against the page, the margin, the column or the paragraph, and text wraps around it. These do not appear in inline_shapes, whose position properties would be meaningless for them.
#### embedded_objects
```
embedded_objects: List[EmbeddedObject]
```
The OLE objects embedded in the document body, in document order.
An embedded object is a whole file carried inside the document — a spreadsheet, a PDF, another document — which Word opens in its own application on double-click. Extracting them is the useful half:
```
for obj in document.embedded_objects:
if obj.blob is not None:
Path(obj.filename or "attachment").write_bytes(obj.blob)
```
Objects in a header, a footer or a footnote belong to those parts and are not included; reach them through the container concerned.
#### images
```
images: Tuple[Image, ...]
```
The distinct images embedded in this document's body, in relationship order.
This is the package-level view, the counterpart of reaching an image through the shape that displays it. Several shapes can share one image part, so this is shorter than inline_shapes whenever a picture is used twice, and it includes images no shape displays — a picture left behind when its paragraph was deleted, for instance.
Only images related from the main document part appear here. A picture in a header, a footer or a comment belongs to that part's relationships instead.
A *linked* image is not included: its bytes are not in the package. Neither is a relationship of image type whose target is not an image part, which does occur — see the same guard in `Package._gather_image_parts()`.
#### theme
```
theme: Theme | None
```
The document's Theme, or `None` when it carries no theme part.
The theme is where a theme typeface token such as `"minorHAnsi"` becomes a real font name, and where a theme colour becomes an RGB value:
```
document.theme.minor_font.latin # -> 'Calibri'
document.theme.color("accent1")
```
For the large class of documents that set no explicit `w:rFonts/@w:ascii` anywhere, this is the only place the typeface the text is actually rendered in can be found; see also Font.theme_typeface.
#### inline_shapes
```
inline_shapes
```
The InlineShapes collection for this document.
An inline shape is a graphical object, such as a picture, contained in a run of text and behaving like a character glyph, being flowed like other text in a paragraph.
#### content_controls
```
content_controls: List[ContentControl]
```
The structured document tags (content controls) in the document body.
In document order, outermost first. The content of a control appears in `.paragraphs`, `.tables` and `.iter_inner_content()` as though the wrapper were not there; this is how the wrapper itself is reached.
#### math
```
math: List[Math]
```
The equations in the document body, in document order.
Equations in a header, a footer, a footnote or a comment are in those parts rather than the body and are not included; reach them through the container concerned. See Paragraph.math for why equation text is not part of Paragraph.text.
#### numbering
```
numbering: Numbering
```
A Numbering object providing access to the list definitions of this document.
The numbering part is created the first time this is used, so a document that never touches it gains no `/word/numbering.xml`.
#### list_numbers
```
list_numbers: List[tuple[Paragraph, str]]
```
`(paragraph, number)` for each list paragraph in the body, in document order.
The number is what a reader sees — "1.", "a)", "iii." — which Word computes from `numbering.xml` at display time rather than storing in the body:
```
for paragraph, number in document.list_numbers:
print(number, paragraph.text)
```
Paragraphs inside tables are included, since they count towards the same lists. This walks the document once, which is why it exists alongside Paragraph.list_number: reading that for every paragraph is quadratic.
#### paragraphs
```
paragraphs: List[Paragraph]
```
The Paragraph instances in the document, in document order.
A paragraph wrapped in a `w:sdt` (content control) appears in this list, in the position of its wrapper.
A revision mark such as `w:ins` or `w:del` wraps runs rather than paragraphs, so it does not affect which paragraphs appear here; it affects their text. See Paragraph.text and Paragraph.original_text.
#### part
```
part: DocumentPart
```
The DocumentPart object of this document.
#### is_template
```
is_template: bool
```
`True` when this document is a Word template, a `.dotx` or `.dotm`.
A template holds the same markup as a document and differs only in the content type of its main part, which is what tells Word to start a new document from it rather than open it for editing.
#### revisions
```
revisions: List[Revision]
```
A Revision for each tracked change in the document body, in document order.
Empty for a document that has not been through review. Revisions in a header, footer or footnote are not in the document part and so are not included; reach those through the paragraphs of the story concerned.
#### sections
```
sections: Sections
```
Sections object providing access to each section in this document.
#### watermarks
```
watermarks: List[Watermark]
```
Every watermark in the document, in section and header order.
Empty when the document has none. Ordinarily one per header rather than one per document, since a watermark is a shape in a header and each header carries its own.
#### settings
```
settings: Settings
```
A Settings object providing access to the document-level settings.
#### styles
```
styles
```
A Styles object providing access to the styles in this document.
#### tables
```
tables: List[Table]
```
All Table instances in the document, in document order.
Note that only tables appearing at the top level of the document appear in this list; a table nested inside a table cell does not appear. A table wrapped in a `w:sdt` (content control) does appear. A row marked as inserted or deleted appears as an ordinary row; see revisions.
#### add_alt_chunk
```
add_alt_chunk(
chunk: bytes | str | PathLike[str] | IO[bytes],
content_type: str,
) -> AltChunk
```
Return an AltChunk newly added at the end of the document body.
`chunk` is the embedded document, given as bytes, as a path to a file (a string or `os.PathLike`), or as a file-like object open for binary read. `content_type` states its format, e.g. `"text/html"`, `"application/rtf"` or `"application/vnd.openxmlformats-officedocument.wordprocessingml.document"`; Word chooses an importer from it, so it must be right.
Word performs the import when it opens the document, which means the embedded content is not visible to this library. Its paragraphs and tables do not appear in `Document.paragraphs`, `Document.tables` or `Document.iter_inner_content()`, and it contributes no styles, numbering or images to this document until Word has rewritten the file.
Source code in `src/docx/document.py`
```
def add_alt_chunk(
self, chunk: bytes | str | os.PathLike[str] | IO[bytes], content_type: str
) -> AltChunk:
"""Return an |AltChunk| newly added at the end of the document body.
`chunk` is the embedded document, given as bytes, as a path to a file (a string
or ``os.PathLike``), or as a file-like object open for binary read.
`content_type` states its format, e.g. `"text/html"`, `"application/rtf"` or
`"application/vnd.openxmlformats-officedocument.wordprocessingml.document"`;
Word chooses an importer from it, so it must be right.
Word performs the import when it opens the document, which means the embedded
content is not visible to this library. Its paragraphs and tables do not appear
in `Document.paragraphs`, `Document.tables` or `Document.iter_inner_content()`,
and it contributes no styles, numbering or images to this document until Word
has rewritten the file.
"""
blob = chunk if isinstance(chunk, bytes) else _read_blob(chunk)
rId = self._part.add_alt_chunk_part(blob, content_type)
altChunk = self._element.body.add_altChunk()
altChunk.rId = rId
return AltChunk(altChunk, self._part)
```
#### add_comment
```
add_comment(
runs: Run | Sequence[Run],
text: str | None = "",
author: str = "",
initials: str | None = "",
) -> Comment
```
Add a comment to the document, anchored to the specified runs.
`runs` can be a single `Run` object or a non-empty sequence of `Run` objects. Only the first and last run of a sequence are used, it's just more convenient to pass a whole sequence when that's what you have handy, like `paragraph.runs` for example. When `runs` contains a single `Run` object, that run serves as both the first and last run.
A comment can be anchored only on an even run boundary, meaning the text the comment "references" must be a non-zero integer number of consecutive runs. The runs need not be *contiguous* per se, like the first can be in one paragraph and the last in the next paragraph, but all runs between the first and the last will be included in the reference.
The comment reference range is delimited by placing a `w:commentRangeStart` element before the first run and a `w:commentRangeEnd` element after the last run. This is why only the first and last run are required and why a single run can serve as both first and last. Word works out which text to highlight in the UI based on these range markers.
`text` allows the contents of a simple comment to be provided in the call, providing for the common case where a comment is a single phrase or sentence without special formatting such as bold or italics. More complex comments can be added using the returned `Comment` object in much the same way as a `Document` or (table) `Cell` object, using methods like `.add_paragraph()`, .add_run()\`, etc.
The `author` and `initials` parameters allow that metadata to be set for the comment. `author` is a required attribute on a comment and is the empty string by default. `initials` is optional on a comment and may be omitted by passing `None`, but Word adds an `initials` attribute by default and we follow that convention by using the empty string when no `initials` argument is provided.
Source code in `src/docx/document.py`
```
def add_comment(
self,
runs: Run | Sequence[Run],
text: str | None = "",
author: str = "",
initials: str | None = "",
) -> Comment:
"""Add a comment to the document, anchored to the specified runs.
`runs` can be a single `Run` object or a non-empty sequence of `Run` objects. Only the
first and last run of a sequence are used, it's just more convenient to pass a whole
sequence when that's what you have handy, like `paragraph.runs` for example. When `runs`
contains a single `Run` object, that run serves as both the first and last run.
A comment can be anchored only on an even run boundary, meaning the text the comment
"references" must be a non-zero integer number of consecutive runs. The runs need not be
_contiguous_ per se, like the first can be in one paragraph and the last in the next
paragraph, but all runs between the first and the last will be included in the reference.
The comment reference range is delimited by placing a `w:commentRangeStart` element before
the first run and a `w:commentRangeEnd` element after the last run. This is why only the
first and last run are required and why a single run can serve as both first and last.
Word works out which text to highlight in the UI based on these range markers.
`text` allows the contents of a simple comment to be provided in the call, providing for
the common case where a comment is a single phrase or sentence without special formatting
such as bold or italics. More complex comments can be added using the returned `Comment`
object in much the same way as a `Document` or (table) `Cell` object, using methods like
`.add_paragraph()`, .add_run()`, etc.
The `author` and `initials` parameters allow that metadata to be set for the comment.
`author` is a required attribute on a comment and is the empty string by default.
`initials` is optional on a comment and may be omitted by passing |None|, but Word adds an
`initials` attribute by default and we follow that convention by using the empty string
when no `initials` argument is provided.
"""
# -- normalize `runs` to a sequence of runs --
runs = [runs] if isinstance(runs, Run) else runs
first_run = runs[0]
last_run = runs[-1]
# -- Note that comments can only appear in the document part --
comment = self.comments.add_comment(text=text, author=author, initials=initials)
# -- let the first run orchestrate placement of the comment range start and end --
first_run.mark_comment_range(last_run, comment.comment_id)
return comment
```
#### add_caption
```
add_caption(
label: str,
text: str = "",
*,
style: str | None = "Caption",
separator: str = " ",
restart_at_heading_level: int | None = None,
before: Paragraph | None = None,
) -> Caption
```
Add a numbered, cross-referenceable caption and return it.
Word numbers each `label` series independently and renumbers the whole series when one is inserted, which is the point of using a `SEQ` field rather than a typed number. The number is therefore *not* in the document until Word computes it; set Settings.update_fields_on_open to have it do so on open.
The caption is bookmarked with a `_Ref`-prefixed name and the returned object carries it, so a cross-reference is a one-liner:
```
caption = document.add_caption("Figure", "Cross-section of the assembly")
document.add_paragraph().add_field(
fields.cross_reference(caption.bookmark_name)
)
```
The `_Ref` naming is not decoration: Word's own cross-reference dialogue offers only targets whose bookmark name follows it, so a caption bookmarked with an arbitrary name is one the user cannot reference from the UI.
`style` is the paragraph style, "Caption" by default, which is what Word uses; pass `None` to leave the paragraph unstyled. `separator` goes between the number and `text`. `restart_at_heading_level` restarts the numbering at each heading of that level, giving the "Figure 2-1" style. `before` places the caption immediately before an existing paragraph, which is where a table caption goes.
Source code in `src/docx/document.py`
```
def add_caption(
self,
label: str,
text: str = "",
*,
style: str | None = "Caption",
separator: str = " ",
restart_at_heading_level: int | None = None,
before: Paragraph | None = None,
) -> Caption:
"""Add a numbered, cross-referenceable caption and return it.
Word numbers each `label` series independently and renumbers the whole series
when one is inserted, which is the point of using a `SEQ` field rather than a
typed number. The number is therefore *not* in the document until Word computes
it; set :attr:`.Settings.update_fields_on_open` to have it do so on open.
The caption is bookmarked with a `_Ref`-prefixed name and the returned object
carries it, so a cross-reference is a one-liner::
caption = document.add_caption("Figure", "Cross-section of the assembly")
document.add_paragraph().add_field(
fields.cross_reference(caption.bookmark_name)
)
The `_Ref` naming is not decoration: Word's own cross-reference dialogue offers
only targets whose bookmark name follows it, so a caption bookmarked with an
arbitrary name is one the user cannot reference from the UI.
`style` is the paragraph style, "Caption" by default, which is what Word uses;
pass |None| to leave the paragraph unstyled. `separator` goes between the number
and `text`. `restart_at_heading_level` restarts the numbering at each heading of
that level, giving the "Figure 2-1" style. `before` places the caption
immediately before an existing paragraph, which is where a table caption goes.
"""
from docx.caption import add_caption
return add_caption(
self._body,
label,
text,
style=style,
separator=separator,
restart_at_heading_level=restart_at_heading_level,
before=before,
)
```
#### add_heading
```
add_heading(text: str = '', level: int = 1)
```
Return a heading paragraph newly added to the end of the document.
The heading paragraph will contain `text` and have its paragraph style determined by `level`. If `level` is 0, the style is set to `Title`. If `level` is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading {level}`. Raises `ValueError` if `level` is outside the range 0-9.
Source code in `src/docx/document.py`
```
def add_heading(self, text: str = "", level: int = 1):
"""Return a heading paragraph newly added to the end of the document.
The heading paragraph will contain `text` and have its paragraph style
determined by `level`. If `level` is 0, the style is set to `Title`. If `level`
is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading
{level}`. Raises |ValueError| if `level` is outside the range 0-9.
"""
if not 0 <= level <= 9:
raise ValueError("level must be in range 0-9, got %d" % level)
style = "Title" if level == 0 else "Heading %d" % level
return self.add_paragraph(text, style)
```
#### add_page_break
```
add_page_break()
```
Return newly Paragraph object containing only a page break.
Source code in `src/docx/document.py`
```
def add_page_break(self):
"""Return newly |Paragraph| object containing only a page break."""
paragraph = self.add_paragraph()
paragraph.add_run().add_break(WD_BREAK.PAGE)
return paragraph
```
#### add_paragraph
```
add_paragraph(
text: str = "",
style: str | ParagraphStyle | None = None,
) -> Paragraph
```
Return paragraph newly added to the end of the document.
The paragraph is populated with `text` and having paragraph style `style`.
`text` can contain tab (`\t`) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (`\n`) or carriage return (`\r`) characters, each of which is converted to a line break.
Source code in `src/docx/document.py`
```
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph:
"""Return paragraph newly added to the end of the document.
The paragraph is populated with `text` and having paragraph style `style`.
`text` can contain tab (``\\t``) characters, which are converted to the
appropriate XML form for a tab. `text` can also include newline (``\\n``) or
carriage return (``\\r``) characters, each of which is converted to a line
break.
"""
return self._body.add_paragraph(text, style)
```
#### add_picture
```
add_picture(
image_path_or_stream: str | PathLike[str] | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
description: str | None = None,
title: str | None = None,
svg_fallback: str
| PathLike[str]
| IO[bytes]
| None = None,
honor_exif_orientation: bool = True,
)
```
Return new picture shape added in its own paragraph at end of the document.
The picture contains the image at `image_path_or_stream`, scaled based on `width` and `height`. If neither width nor height is specified, the picture appears at its native size. If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. The native size of the picture is calculated using the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case.
`description` is the picture's alternative text, which is what a screen reader announces and what an accessibility check looks for; `title` is the separate caption-like field Word writes alongside it.
`svg_fallback` is the raster image shown in place of an SVG wherever the vector source cannot be rendered, and `honor_exif_orientation` applies a photo's EXIF `Orientation` as a rotation in the DrawingML; see `Run.add_picture()` for both.
Source code in `src/docx/document.py`
```
def add_picture(
self,
image_path_or_stream: str | os.PathLike[str] | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
description: str | None = None,
title: str | None = None,
svg_fallback: str | os.PathLike[str] | IO[bytes] | None = None,
honor_exif_orientation: bool = True,
):
"""Return new picture shape added in its own paragraph at end of the document.
The picture contains the image at `image_path_or_stream`, scaled based on
`width` and `height`. If neither width nor height is specified, the picture
appears at its native size. If only one is specified, it is used to compute a
scaling factor that is then applied to the unspecified dimension, preserving the
aspect ratio of the image. The native size of the picture is calculated using
the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi
if no value is specified, as is often the case.
`description` is the picture's alternative text, which is what a screen reader
announces and what an accessibility check looks for; `title` is the separate
caption-like field Word writes alongside it.
`svg_fallback` is the raster image shown in place of an SVG wherever the vector
source cannot be rendered, and `honor_exif_orientation` applies a photo's EXIF
`Orientation` as a rotation in the DrawingML; see `Run.add_picture()` for both.
"""
run = self.add_paragraph().add_run()
return run.add_picture(
image_path_or_stream,
width,
height,
description=description,
title=title,
svg_fallback=svg_fallback,
honor_exif_orientation=honor_exif_orientation,
)
```
#### add_section
```
add_section(start_type: WD_SECTION = NEW_PAGE)
```
Return a Section object newly added at the end of the document.
The optional `start_type` argument must be a member of the WdSectionStart enumeration, and defaults to `WD_SECTION.NEW_PAGE` if not provided.
Source code in `src/docx/document.py`
```
def add_section(self, start_type: WD_SECTION = WD_SECTION.NEW_PAGE):
"""Return a |Section| object newly added at the end of the document.
The optional `start_type` argument must be a member of the :ref:`WdSectionStart`
enumeration, and defaults to ``WD_SECTION.NEW_PAGE`` if not provided.
"""
new_sectPr = self._element.body.add_section_break()
new_sectPr.start_type = start_type
return Section(new_sectPr, self._part)
```
#### add_table
```
add_table(
rows: int,
cols: int,
style: str | _TableStyle | None = None,
*,
title: str | None = None,
description: str | None = None,
)
```
Add a table having row and column counts of `rows` and `cols` respectively.
`style` may be a table style object or a table style name. If `style` is `None`, the table inherits the default table style of the document.
`description` is the table's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, caption-like field Word writes alongside it. Both are omitted from the XML when `None`.
Source code in `src/docx/document.py`
```
def add_table(
self,
rows: int,
cols: int,
style: str | _TableStyle | None = None,
*,
title: str | None = None,
description: str | None = None,
):
"""Add a table having row and column counts of `rows` and `cols` respectively.
`style` may be a table style object or a table style name. If `style` is |None|,
the table inherits the default table style of the document.
`description` is the table's alternative text, which is what a screen reader
announces and what an accessibility check looks for. `title` is the separate,
caption-like field Word writes alongside it. Both are omitted from the XML when
|None|.
"""
table = self._body.add_table(
rows, cols, self._block_width, title=title, description=description
)
table.style = style
return table
```
#### bookmarks
```
bookmarks() -> Bookmarks
```
The Bookmarks in this document, in document order.
Bookmarks Word maintains for itself, such as `_GoBack` and the `_Toc…` anchors, are left out of the collection; reach them through `.iter_all()`.
Source code in `src/docx/document.py`
```
@lazyproperty
def bookmarks(self) -> Bookmarks:
"""The |Bookmarks| in this document, in document order.
Bookmarks Word maintains for itself, such as `_GoBack` and the `_Toc…` anchors,
are left out of the collection; reach them through `.iter_all()`.
"""
return Bookmarks(self._element, self._part)
```
#### cleanup
```
cleanup(
*,
styles: bool = True,
numbering: bool = True,
media: bool = True,
latent_styles: bool = False,
keep: Tuple[str, ...] = (),
) -> CleanupResult
```
Remove what this document carries that nothing points at; report what went.
A document created by this library defines 164 styles and references one, and carries numbering definitions for lists it does not have:
```
>>> print(document.cleanup())
removed 152 styles, 3 numbering definitions, ...
```
Three separate kinds of dead weight, each with its own flag: unused style definitions, numbering definitions no content or style references, and image parts nothing in the document part refers to — the last of which the `.delete()` methods leave behind as a matter of course.
**This is destructive.** For styles, the reachability closure in Styles.usage is the only thing standing between it and a document whose formatting has quietly changed; `keep` names styles to preserve along with their dependencies, for ones you plan to apply but have not yet.
`latent_styles` is off by default and separate from `styles` on purpose: dropping a `w:lsdException` changes what a user sees in Word's style gallery rather than how the document renders.
Source code in `src/docx/document.py`
```
def cleanup(
self,
*,
styles: bool = True,
numbering: bool = True,
media: bool = True,
latent_styles: bool = False,
keep: Tuple[str, ...] = (),
) -> CleanupResult:
"""Remove what this document carries that nothing points at; report what went.
A document created by this library defines 164 styles and references one, and
carries numbering definitions for lists it does not have::
>>> print(document.cleanup())
removed 152 styles, 3 numbering definitions, ...
Three separate kinds of dead weight, each with its own flag: unused style
definitions, numbering definitions no content or style references, and image
parts nothing in the document part refers to — the last of which the `.delete()`
methods leave behind as a matter of course.
**This is destructive.** For styles, the reachability closure in
:meth:`.Styles.usage` is the only thing standing between it and a document whose
formatting has quietly changed; `keep` names styles to preserve along with their
dependencies, for ones you plan to apply but have not yet.
`latent_styles` is off by default and separate from `styles` on purpose:
dropping a `w:lsdException` changes what a user sees in Word's style gallery
rather than how the document renders.
"""
from docx.cleanup import cleanup
return cleanup(
self._part,
styles=styles,
numbering=numbering,
media=media,
latent_styles=latent_styles,
keep=keep,
)
```
#### add_custom_xml_part
```
add_custom_xml_part(
xml: str | bytes,
schema_refs: Tuple[str, ...] = (),
*,
item_id: str | None = None,
) -> CustomXmlPart
```
Add an item to the custom XML data store and return its part.
The custom XML data store is where a document-generation pipeline keeps its data: whole XML documents against a caller-supplied schema, which content controls in the document bind to through `w:dataBinding` and Word keeps in step with what it displays:
```
document.add_custom_xml_part(
"42.00",
schema_refs=("urn:example:invoice",),
)
```
This is a different thing from custom_properties, which is a flat list of named scalars in `docProps/custom.xml`.
A `customXml/itemN.xml` part is created for `xml`, along with the `itemPropsN.xml` sidecar Word identifies it by, carrying the namespaces named in `schema_refs` and a GUID.
`item_id` is that GUID, in Word's `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"` shape. One is generated at random when it is omitted, which is what Word does — but a random value is the one thing in this library's output that is not a function of its input, so pass an `item_id` of your own when byte-reproducible output matters. It only has to be unique within the document.
Source code in `src/docx/document.py`
```
def add_custom_xml_part(
self,
xml: str | bytes,
schema_refs: Tuple[str, ...] = (),
*,
item_id: str | None = None,
) -> CustomXmlPart:
"""Add an item to the custom XML data store and return its part.
The custom XML data store is where a document-generation pipeline keeps its
data: whole XML documents against a caller-supplied schema, which content
controls in the document bind to through `w:dataBinding` and Word keeps in step
with what it displays::
document.add_custom_xml_part(
"42.00",
schema_refs=("urn:example:invoice",),
)
This is a different thing from :attr:`custom_properties`, which is a flat list
of named scalars in `docProps/custom.xml`.
A `customXml/itemN.xml` part is created for `xml`, along with the
`itemPropsN.xml` sidecar Word identifies it by, carrying the namespaces named in
`schema_refs` and a GUID.
`item_id` is that GUID, in Word's `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"`
shape. One is generated at random when it is omitted, which is what Word does —
but a random value is the one thing in this library's output that is not a
function of its input, so pass an `item_id` of your own when byte-reproducible
output matters. It only has to be unique within the document.
"""
return self._part.add_custom_xml_part(xml, schema_refs, item_id=item_id)
```
#### remove_vba_project
```
remove_vba_project() -> int
```
Remove this document's VBA project; return how many parts were removed.
The `word/vbaData.xml` sibling, which holds command-bar and macro-name customisations, goes with it rather than being left orphaned. Zero for a document that carries no project. Equivalent to `del document.vba_project`.
Source code in `src/docx/document.py`
```
def remove_vba_project(self) -> int:
"""Remove this document's VBA project; return how many parts were removed.
The `word/vbaData.xml` sibling, which holds command-bar and macro-name
customisations, goes with it rather than being left orphaned. Zero for a
document that carries no project. Equivalent to ``del document.vba_project``.
"""
return self._part.remove_vba_project()
```
#### iter_inner_content
```
iter_inner_content() -> Iterator[Paragraph | Table]
```
Generate each `Paragraph` or `Table` in this document in document order.
Source code in `src/docx/document.py`
```
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each `Paragraph` or `Table` in this document in document order."""
return self._body.iter_inner_content()
```
#### replace_text
```
replace_text(
old: str,
new: str,
*,
count: int = -1,
regex: bool = False,
flags: int = 0,
tables: bool = True,
headers_footers: bool = False,
footnotes: bool = False,
) -> int
```
Replace occurrences of `old` with `new` in this document; return how many.
The match is made against each paragraph's text as a whole, so it succeeds whether or not Word split the text across runs; see Paragraph.replace_text for what happens to formatting.
What gets searched is explicit rather than incidental, because "replace it everywhere" means different things to different callers and getting it wrong is invisible until someone reads the header:
```
document.replace_text("{{name}}", "Ada") # body only
document.replace_text("{{name}}", "Ada", headers_footers=True) # and those
```
The document body, including tables unless `tables` is `False`, is always searched. Headers and footers of every section — default, first-page and even-page alike — are searched when `headers_footers` is `True`, and footnotes and endnotes when `footnotes` is `True`. Comments are never searched: a comment is somebody's remark about the document rather than part of it.
`count` of -1 replaces every match; any other value limits the total across everything searched, in the order given above. `regex` and `flags` are as for Paragraph.replace_text.
Source code in `src/docx/document.py`
```
def replace_text(
self,
old: str,
new: str,
*,
count: int = -1,
regex: bool = False,
flags: int = 0,
tables: bool = True,
headers_footers: bool = False,
footnotes: bool = False,
) -> int:
"""Replace occurrences of `old` with `new` in this document; return how many.
The match is made against each paragraph's text as a whole, so it succeeds
whether or not Word split the text across runs; see
:meth:`.Paragraph.replace_text` for what happens to formatting.
What gets searched is explicit rather than incidental, because "replace it
everywhere" means different things to different callers and getting it wrong is
invisible until someone reads the header::
document.replace_text("{{name}}", "Ada") # body only
document.replace_text("{{name}}", "Ada", headers_footers=True) # and those
The document body, including tables unless `tables` is |False|, is always
searched. Headers and footers of every section — default, first-page and
even-page alike — are searched when `headers_footers` is |True|, and footnotes
and endnotes when `footnotes` is |True|. Comments are never searched: a comment
is somebody's remark about the document rather than part of it.
`count` of -1 replaces every match; any other value limits the total across
everything searched, in the order given above. `regex` and `flags` are as for
:meth:`.Paragraph.replace_text`.
"""
if count == 0:
return 0
containers: List[BlockItemContainer] = [self._body]
if headers_footers:
for section in self.sections:
# -- a header that inherits from the prior section has no definition of
# -- its own; searching it would visit the inherited one a second time,
# -- and merely reaching for it would create a part in the first section
containers.extend(
hdrftr
for hdrftr in section.iter_headers_footers()
if not hdrftr.is_linked_to_previous
)
if footnotes and self._part.has_footnotes_part:
containers.extend(self.footnotes)
if footnotes and self._part.has_endnotes_part:
containers.extend(self.endnotes)
replaced = 0
for container in containers:
replaced += container.replace_text(
old,
new,
count=-1 if count < 0 else count - replaced,
regex=regex,
flags=flags,
tables=tables,
)
if count >= 0 and replaced >= count:
break
return replaced
```
#### save
```
save(
path_or_stream: str | PathLike[str] | IO[bytes],
as_template: bool | None = None,
)
```
Save this document to `path_or_stream`.
`path_or_stream` can be either a path to a filesystem location (a string or `os.PathLike`) or a file-like object.
`as_template` selects whether the result is a Word template (`.dotx` / `.dotm`) or an ordinary document (`.docx` / `.docm`). The default of `None` keeps whichever this document already is, so a template opened and saved is still a template. Pass `False` to generate a document from a template, or `True` to turn a document into one. Macro-enabled input stays macro-enabled either way.
Note this sets the content type; it does not choose the file extension for you.
Source code in `src/docx/document.py`
```
def save(
self,
path_or_stream: str | os.PathLike[str] | IO[bytes],
as_template: bool | None = None,
):
"""Save this document to `path_or_stream`.
`path_or_stream` can be either a path to a filesystem location (a string or
``os.PathLike``) or a file-like object.
`as_template` selects whether the result is a Word template (``.dotx`` /
``.dotm``) or an ordinary document (``.docx`` / ``.docm``). The default of
|None| keeps whichever this document already is, so a template opened and saved
is still a template. Pass ``False`` to generate a document from a template, or
``True`` to turn a document into one. Macro-enabled input stays macro-enabled
either way.
Note this sets the content type; it does not choose the file extension for you.
"""
if as_template is not None:
self._part.content_type = _document_content_type(
self._part.content_type, as_template=as_template
)
if isinstance(path_or_stream, os.PathLike):
path_or_stream = os.fspath(path_or_stream)
self._part.save(path_or_stream)
```
#### accept_all_revisions
```
accept_all_revisions() -> int
```
Accept every tracked change in the document body; return how many.
Insertions become ordinary text, deletions go, formatting-change records are dropped leaving the current formatting, and a deleted paragraph mark merges its paragraph with the one after it. The result is the document as Paragraph.text already reads it.
Source code in `src/docx/document.py`
```
def accept_all_revisions(self) -> int:
"""Accept every tracked change in the document body; return how many.
Insertions become ordinary text, deletions go, formatting-change records are
dropped leaving the current formatting, and a deleted paragraph mark merges its
paragraph with the one after it. The result is the document as
:attr:`.Paragraph.text` already reads it.
"""
from docx.revisions import apply_all
return apply_all(self._element, self._part, accept=True)
```
#### reject_all_revisions
```
reject_all_revisions() -> int
```
Reject every tracked change in the document body; return how many.
The reverse of accept_all_revisions: the result is the document as Paragraph.original_text reads it.
Source code in `src/docx/document.py`
```
def reject_all_revisions(self) -> int:
"""Reject every tracked change in the document body; return how many.
The reverse of :meth:`accept_all_revisions`: the result is the document as
:attr:`.Paragraph.original_text` reads it.
"""
from docx.revisions import apply_all
return apply_all(self._element, self._part, accept=False)
```
#### add_text_watermark
```
add_text_watermark(
text: str,
*,
font: str = "Calibri",
font_size: Length | int | None = None,
color: str = "C0C0C0",
opacity: float | None = None,
angle: float = 315,
width: Length | int = Pt(468),
height: Length | int = Pt(234),
bold: bool = False,
italic: bool = False,
) -> List[Watermark]
```
Add a text watermark to the whole document, returning the watermarks added.
The faint "DRAFT" or "CONFIDENTIAL" behind the content:
```
document.add_text_watermark("DRAFT")
document.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0)
```
Every section is covered, and within each the default, first-page and even-page headers alike, so the watermark does not disappear on a page that uses a different header. A header shared between sections is written to once.
The arguments are as for Section.add_text_watermark, which is also how a watermark is applied to one section rather than the whole document.
Source code in `src/docx/document.py`
```
def add_text_watermark(
self,
text: str,
*,
font: str = "Calibri",
font_size: Length | int | None = None,
color: str = "C0C0C0",
opacity: float | None = None,
angle: float = 315,
width: Length | int = Pt(468),
height: Length | int = Pt(234),
bold: bool = False,
italic: bool = False,
) -> List[Watermark]:
"""Add a text watermark to the whole document, returning the watermarks added.
The faint "DRAFT" or "CONFIDENTIAL" behind the content::
document.add_text_watermark("DRAFT")
document.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0)
Every section is covered, and within each the default, first-page and even-page
headers alike, so the watermark does not disappear on a page that uses a
different header. A header shared between sections is written to once.
The arguments are as for :meth:`.Section.add_text_watermark`, which is also how
a watermark is applied to one section rather than the whole document.
"""
from docx.watermark import add_text_watermark, iter_watermark_headers
return add_text_watermark(
iter_watermark_headers(self.sections),
text,
font=font,
font_size=font_size,
color=color,
opacity=opacity,
angle=angle,
width=width,
height=height,
bold=bold,
italic=italic,
)
```
#### add_image_watermark
```
add_image_watermark(
image_path_or_stream: 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 the whole document; see add_text_watermark.
`washout` applies Word's brightness-and-contrast correction, which is what makes a logo read as a background rather than sitting opaquely over the text.
Source code in `src/docx/document.py`
```
def add_image_watermark(
self,
image_path_or_stream: 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 the whole document; see :meth:`add_text_watermark`.
`washout` applies Word's brightness-and-contrast correction, which is what makes
a logo read as a background rather than sitting opaquely over the text.
"""
from docx.watermark import add_image_watermark, iter_watermark_headers
return add_image_watermark(
iter_watermark_headers(self.sections),
image_path_or_stream,
width=width,
height=height,
washout=washout,
scale=scale,
)
```
#### remove_watermark
```
remove_watermark() -> int
```
Remove every watermark from the document, returning how many were removed.
Source code in `src/docx/document.py`
```
def remove_watermark(self) -> int:
"""Remove every watermark from the document, returning how many were removed."""
from docx.watermark import iter_watermark_headers, remove_watermarks
return remove_watermarks(iter_watermark_headers(self.sections))
```
### \_Body
```
_Body(body_elm: CT_Body, parent: ProvidesStoryPart)
```
Bases: `BlockItemContainer`
Proxy for `` element in this document.
It's primary role is a container for document content.
Source code in `src/docx/document.py`
```
def __init__(self, body_elm: CT_Body, parent: t.ProvidesStoryPart):
super(_Body, self).__init__(body_elm, parent)
self._body = body_elm
```
#### clear_content
```
clear_content() -> _Body
```
Return this \_Body instance after clearing it of all content.
Section properties for the main document story, if present, are preserved.
Source code in `src/docx/document.py`
```
def clear_content(self) -> _Body:
"""Return this |_Body| instance after clearing it of all content.
Section properties for the main document story, if present, are preserved.
"""
self._body.clear_content()
return self
```
## exceptions
Exceptions used with python-docx.
The base exception class is PythonDocxError.
### PythonDocxError
Bases: `Exception`
Generic error class.
### InvalidSpanError
Bases: `PythonDocxError`
Raised when an invalid merge region is specified in a request to merge table cells.
### InvalidXmlError
Bases: `PythonDocxError`
Raised when invalid XML is encountered, such as on attempt to access a missing required child element.
### StrictOoxmlNotSupportedError
Bases: `PythonDocxError`
Raised on opening an ISO/IEC 29500 Strict document.
Word's "Strict Open XML Document" save format writes the same element names in the Strict namespaces (`http://purl.oclc.org/ooxml/...`) rather than the Transitional ones this library reads. The file is an ordinary-looking `.docx`, so without this the failure is an `AttributeError` naming an lxml internal, which says nothing about what is actually wrong or what to do about it.
Strict is the default in some regulated and public-sector environments. Re-saving the file from Word as "Word Document (.docx)" produces the Transitional form.
## fields
The Field object and the instruction builders that go with it.
A field is how Word represents anything it works out for itself: page numbers, a table of contents, cross-references, captions that renumber, dates, and references to document properties. Every one of those is the same feature with a different instruction string.
Word writes a field in one of two forms. A **simple field** is self-contained — the instruction is an attribute of `w:fldSimple` and the cached result is its content:
```
7
```
A **complex field** is spread across sibling runs, delimited by field characters:
```
PAGE 7
```
Both forms are read here and Field presents them the same way. Complex fields nest — a `TOC` result is full of `PAGEREF` fields — and the nesting is tracked, so an inner field is a field in its own right and its text also counts towards the outer field's result.
**This library cannot compute a field result.** A table of contents added here is empty, a `PAGE` field has no number, and a cross-reference shows nothing, because all three depend on how Word lays the document out. Fields are written with `w:dirty="true"` so Word refreshes them when it opens the document; setting Settings.update_fields_on_open asks it to refresh every field in the document, which is what a generated table of contents needs. No amount of API changes this.
### Field
```
Field(
element: CT_SimpleField | CT_FldChar,
parent: ProvidesStoryPart,
instruction: str = "",
result_text: str = "",
)
```
Bases: `StoryChild`
A field in a document — a page number, a table of contents, a cross-reference.
Not constructed directly; reached through Paragraph.fields, Document.fields or as the return value of Paragraph.add_field.
Source code in `src/docx/fields.py`
```
def __init__(
self,
element: CT_SimpleField | CT_FldChar,
parent: t.ProvidesStoryPart,
instruction: str = "",
result_text: str = "",
):
super().__init__(parent)
self._element = element
self._instruction = instruction
self._result_text = result_text
```
#### dirty
```
dirty: bool
```
True when Word will refresh this field the next time it opens the document.
Read/write. A field written by this library is dirty by default, since its cached result is empty and only Word can fill it in.
#### instruction
```
instruction: str
```
The field instruction, e.g. `' TOC \\o "1-3" \\h '`.
This is the whole instruction including its switches, with the surrounding spaces Word writes. It is the concatenation of every `w:instrText` of a complex field, so an instruction Word split across runs reads as one string here.
#### is_simple
```
is_simple: bool
```
True when this is a `w:fldSimple` rather than a complex field.
#### result_text
```
result_text: str
```
The result Word last rendered for this field, the empty string if none.
A field this library has just added has no result: only Word can compute one.
#### text
```
text: str
```
The text this field displays, which is its result_text.
#### type
```
type: str | None
```
The field type in upper case — `"PAGE"`, `"TOC"`, `"REF"` — or `None`.
This is the first token of the instruction. `None` when the instruction is empty or begins with a switch, which is malformed but does occur.
A plain string rather than an enumeration: ISO/IEC 29500 defines around ninety field types and Word accepts more, so a closed set would reject valid documents.
### \_ComplexFieldBuilder
```
_ComplexFieldBuilder(begin: CT_FldChar, position: int)
```
Accumulates the parts of one complex field while its subtree is walked.
Source code in `src/docx/fields.py`
```
def __init__(self, begin: CT_FldChar, position: int):
self.begin = begin
self.position = position
self.separate_seen = False
self.instruction_parts: List[str] = []
self.result_parts: List[str] = []
```
### iter_fields
```
iter_fields(
element: _Element, parent: ProvidesStoryPart
) -> Iterator[Field]
```
Generate a Field for each field in the subtree of `element`.
Fields appear in document order, outermost first. A complex field nested inside another — a `PAGEREF` in a table-of-contents entry — is generated in its own right, after the field containing it.
A complex field with no "end" field-character is malformed and is skipped rather than raising: such a document exists in the wild and reading the fields that are well-formed is more useful than refusing the whole document.
Source code in `src/docx/fields.py`
```
def iter_fields(element: _Element, parent: t.ProvidesStoryPart) -> Iterator[Field]:
"""Generate a |Field| for each field in the subtree of `element`.
Fields appear in document order, outermost first. A complex field nested inside
another — a `PAGEREF` in a table-of-contents entry — is generated in its own right,
after the field containing it.
A complex field with no "end" field-character is malformed and is skipped rather
than raising: such a document exists in the wild and reading the fields that are
well-formed is more useful than refusing the whole document.
"""
stack: List[_ComplexFieldBuilder] = []
complete: List[tuple[int, Field]] = []
for position, node in enumerate(cast("List[_Element]", element.xpath(_FIELD_NODE_XPATH))):
tag = node.tag
if tag == qn("w:fldSimple"):
simple = cast("CT_SimpleField", node)
# -- read `w:instr` off the attribute rather than through the declared
# -- required attribute, which raises for the malformed `w:fldSimple`
# -- lacking one; a missing instruction is an empty one here --
instr = node.get(qn("w:instr")) or ""
complete.append((position, Field(simple, parent, instr, simple.result_text)))
continue
if tag == qn("w:fldChar"):
fldChar = cast("CT_FldChar", node)
fldCharType = node.get(qn("w:fldCharType"))
if fldCharType == "begin":
stack.append(_ComplexFieldBuilder(fldChar, position))
elif fldCharType == "separate":
if stack:
stack[-1].separate_seen = True
elif fldCharType == "end" and stack:
builder = stack.pop()
complete.append((builder.position, builder.build(parent)))
continue
if tag == qn("w:instrText"):
# -- an instruction belongs to the innermost field that has not yet reached
# -- its "separate"; past that point the field is showing its result --
for builder in reversed(stack):
if not builder.separate_seen:
builder.instruction_parts.append(str(node))
break
continue
# -- a text-bearing element: part of the result of every enclosing field that
# -- has passed its "separate" marker --
for builder in stack:
if builder.separate_seen:
builder.result_parts.append(str(node))
return (field for _, field in sorted(complete, key=lambda pair: pair[0]))
```
### new_complex_field
```
new_complex_field(
instruction: str, dirty: bool = True
) -> List[_Element]
```
The runs of a complex field for `instruction`, ready to append to a paragraph.
No "separate" field-character is written, because there is no cached result to put after one; Word adds both when it computes the result. `dirty` sets `w:dirty` on the "begin" field-character, asking Word to refresh the field when it opens the file.
Source code in `src/docx/fields.py`
```
def new_complex_field(instruction: str, dirty: bool = True) -> List[_Element]:
"""The runs of a complex field for `instruction`, ready to append to a paragraph.
No "separate" field-character is written, because there is no cached result to put
after one; Word adds both when it computes the result. `dirty` sets `w:dirty` on the
"begin" field-character, asking Word to refresh the field when it opens the file.
"""
begin = OxmlElement("w:fldChar")
begin.set(qn("w:fldCharType"), "begin")
if dirty:
begin.set(qn("w:dirty"), "true")
instrText = OxmlElement("w:instrText")
instrText.set(qn("xml:space"), "preserve")
instrText.text = instruction
end = OxmlElement("w:fldChar")
end.set(qn("w:fldCharType"), "end")
runs: List[_Element] = []
for child in (begin, instrText, end):
r = OxmlElement("w:r")
r.append(child)
runs.append(r)
return runs
```
### page_number
```
page_number() -> str
```
A `PAGE` field instruction — the number of the page the field is on.
Source code in `src/docx/fields.py`
```
def page_number() -> str:
"""A `PAGE` field instruction — the number of the page the field is on."""
return " PAGE "
```
### page_count
```
page_count() -> str
```
A `NUMPAGES` field instruction — the number of pages in the document.
Source code in `src/docx/fields.py`
```
def page_count() -> str:
"""A `NUMPAGES` field instruction — the number of pages in the document."""
return " NUMPAGES "
```
### table_of_contents
```
table_of_contents(
levels: tuple[int, int] = (1, 3),
hyperlinks: bool = True,
use_outline_levels: bool = True,
hide_tab_and_page_numbers_in_web: bool = True,
) -> str
```
A `TOC` field instruction, the switches matching what Word's own dialog writes.
`levels` is the inclusive range of heading levels to include. `hyperlinks` makes each entry a link to its heading (`\h`), `use_outline_levels` includes paragraphs given an outline level without a heading style (`\u`), and `hide_tab_and_page_numbers_in_web` is Word's `\z`, which suppresses the leader and page number in web layout where there are no pages.
The table is empty until Word builds it; see the module docstring.
Source code in `src/docx/fields.py`
```
def table_of_contents(
levels: tuple[int, int] = (1, 3),
hyperlinks: bool = True,
use_outline_levels: bool = True,
hide_tab_and_page_numbers_in_web: bool = True,
) -> str:
"""A `TOC` field instruction, the switches matching what Word's own dialog writes.
`levels` is the inclusive range of heading levels to include. `hyperlinks` makes
each entry a link to its heading (`\\h`), `use_outline_levels` includes paragraphs
given an outline level without a heading style (`\\u`), and
`hide_tab_and_page_numbers_in_web` is Word's `\\z`, which suppresses the leader and
page number in web layout where there are no pages.
The table is empty until Word builds it; see the module docstring.
"""
first, last = levels
if not 1 <= first <= last <= 9:
raise ValueError(f"levels must be a range within 1-9, got {levels!r}")
return " TOC {} ".format(
_switches(
f'\\o "{first}-{last}"',
"\\h" if hyperlinks else None,
"\\z" if hide_tab_and_page_numbers_in_web else None,
"\\u" if use_outline_levels else None,
)
)
```
### cross_reference
```
cross_reference(
bookmark: str,
hyperlink: bool = True,
insert_paragraph_number: bool = False,
) -> str
```
A `REF` field instruction referring to `bookmark`.
`hyperlink` makes the reference clickable (`\h`), and `insert_paragraph_number` shows the referenced paragraph's number rather than its text (`\n`).
The bookmark must exist in the document, or Word displays "Error! Bookmark not defined." See Paragraph.add_bookmark for creating one.
Source code in `src/docx/fields.py`
```
def cross_reference(
bookmark: str, hyperlink: bool = True, insert_paragraph_number: bool = False
) -> str:
"""A `REF` field instruction referring to `bookmark`.
`hyperlink` makes the reference clickable (`\\h`), and `insert_paragraph_number`
shows the referenced paragraph's number rather than its text (`\\n`).
The bookmark must exist in the document, or Word displays "Error! Bookmark not
defined." See :meth:`.Paragraph.add_bookmark` for creating one.
"""
return " REF {} ".format(
_switches(
_quote(bookmark),
"\\h" if hyperlink else None,
"\\n" if insert_paragraph_number else None,
)
)
```
### page_reference
```
page_reference(
bookmark: str, hyperlink: bool = True
) -> str
```
A `PAGEREF` field instruction — the page number `bookmark` appears on.
Source code in `src/docx/fields.py`
```
def page_reference(bookmark: str, hyperlink: bool = True) -> str:
"""A `PAGEREF` field instruction — the page number `bookmark` appears on."""
return " PAGEREF {} ".format(_switches(_quote(bookmark), "\\h" if hyperlink else None))
```
### sequence
```
sequence(
name: str, restart_at_heading_level: int | None = None
) -> str
```
A `SEQ` field instruction — the caption numbering of the `name` series.
`name` is the caption label, conventionally "Figure", "Table" or "Equation". Word numbers each series independently and renumbers the whole series when one is inserted, which is the point of using a field rather than a typed number.
`restart_at_heading_level` restarts numbering at each heading of that level (`\s`), giving the "Figure 3-2" style of numbering.
Source code in `src/docx/fields.py`
```
def sequence(name: str, restart_at_heading_level: int | None = None) -> str:
"""A `SEQ` field instruction — the caption numbering of the `name` series.
`name` is the caption label, conventionally "Figure", "Table" or "Equation". Word
numbers each series independently and renumbers the whole series when one is
inserted, which is the point of using a field rather than a typed number.
`restart_at_heading_level` restarts numbering at each heading of that level (`\\s`),
giving the "Figure 3-2" style of numbering.
"""
return " SEQ {} ".format(
_switches(
_quote(name),
"\\* ARABIC",
None if restart_at_heading_level is None else f"\\s {restart_at_heading_level}",
)
)
```
### date
```
date(
format: str | None = None, save_date: bool = False
) -> str
```
A `DATE` field instruction, or `SAVEDATE` when `save_date` is `True`.
`format` is a Word date-time picture such as `"d MMMM yyyy"`; the document's default format is used when it is omitted. Note a `DATE` field shows the date the document was *opened*, not the date it was generated — for a fixed date, write the text rather than a field.
Source code in `src/docx/fields.py`
```
def date(format: str | None = None, save_date: bool = False) -> str:
"""A `DATE` field instruction, or `SAVEDATE` when `save_date` is |True|.
`format` is a Word date-time picture such as ``"d MMMM yyyy"``; the document's
default format is used when it is omitted. Note a `DATE` field shows the date the
document was *opened*, not the date it was generated — for a fixed date, write the
text rather than a field.
"""
keyword = "SAVEDATE" if save_date else "DATE"
return " {} ".format(_switches(keyword, None if format is None else f'\\@ "{format}"'))
```
### doc_property
```
doc_property(name: str) -> str
```
A `DOCPROPERTY` field instruction showing document property `name`.
`name` is a built-in property such as `"Title"` or `"Author"`, or the name of a custom property; see Document.core_properties and Document.custom_properties.
Source code in `src/docx/fields.py`
```
def doc_property(name: str) -> str:
"""A `DOCPROPERTY` field instruction showing document property `name`.
`name` is a built-in property such as ``"Title"`` or ``"Author"``, or the name of a
custom property; see :attr:`.Document.core_properties` and
:attr:`.Document.custom_properties`.
"""
return f" DOCPROPERTY {_quote(name)} "
```
### styleref
```
styleref(
style_name: str, search_from_bottom: bool = False
) -> str
```
A `STYLEREF` field instruction showing the nearest text in `style_name`.
This is how a running header repeats the current chapter title.
Source code in `src/docx/fields.py`
```
def styleref(style_name: str, search_from_bottom: bool = False) -> str:
"""A `STYLEREF` field instruction showing the nearest text in `style_name`.
This is how a running header repeats the current chapter title.
"""
return " STYLEREF {} ".format(
_switches(_quote(style_name), "\\l" if search_from_bottom else None)
)
```
## footnotes
Collections providing access to the footnotes and endnotes of the document.
Footnotes and endnotes are the same feature placed differently — a footnote at the foot of its page, an endnote at the end of the document or section. They share a complex type in the schema and share their implementation here; see `docx.oxml.footnotes`.
### \_Notes
```
_Notes(
notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart
)
```
Common behavior of the Footnotes and Endnotes collections.
Only the notes an author wrote are in the collection. Word keeps two more in the same part, at ids -1 and 0, which hold the rule it draws above the note area and its continuation on the next page; those are structural and never appear here.
Source code in `src/docx/footnotes.py`
```
def __init__(self, notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart):
self._notes_elm = notes_elm
self._notes_part = notes_part
```
### \_Note
```
_Note(note_elm: CT_FtnEdn, notes_part: StoryPart)
```
Bases: `BlockItemContainer`
Common behavior of the Footnote and Endnote proxies.
A note is a block-item container, like a table cell, so it can hold both paragraphs and tables and its paragraphs can hold rich text, hyperlinks and images. The common case is a single paragraph of plain text.
Source code in `src/docx/footnotes.py`
```
def __init__(self, note_elm: CT_FtnEdn, notes_part: StoryPart):
super().__init__(note_elm, notes_part)
self._note_elm = note_elm
```
#### text
```
text: str
```
The text content of this note as a string.
Only content in paragraphs is included, and all emphasis and styling is stripped. Paragraph boundaries are indicated with a newline (`"\\n"`).
#### add_paragraph
```
add_paragraph(
text: str = "",
style: str | ParagraphStyle | None = None,
) -> Paragraph
```
Return a paragraph newly added to the end of this note.
The paragraph holds `text` in a single run if present and is given paragraph style `style`. When `style` is omitted or `None`, the style Word uses for this kind of note's content is applied — "FootnoteText" or "EndnoteText".
Source code in `src/docx/footnotes.py`
```
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph:
"""Return a paragraph newly added to the end of this note.
The paragraph holds `text` in a single run if present and is given paragraph
style `style`. When `style` is omitted or |None|, the style Word uses for this
kind of note's content is applied — "FootnoteText" or "EndnoteText".
"""
paragraph = super().add_paragraph(text, style)
# -- assign the style directly to the element, since `paragraph.style` raises
# -- when the style is not defined in the styles part and Word supplies this one
# -- as a latent style
if style is None:
paragraph._p.style = self._para_style # pyright: ignore[reportPrivateUsage]
return paragraph
```
### Footnote
```
Footnote(note_elm: CT_FtnEdn, notes_part: StoryPart)
```
Bases: `_Note`
Proxy for a single footnote in the document.
Source code in `src/docx/footnotes.py`
```
def __init__(self, note_elm: CT_FtnEdn, notes_part: StoryPart):
super().__init__(note_elm, notes_part)
self._note_elm = note_elm
```
#### footnote_id
```
footnote_id: int
```
The identifier a `w:footnoteReference` uses to cite this footnote.
### Endnote
```
Endnote(note_elm: CT_FtnEdn, notes_part: StoryPart)
```
Bases: `_Note`
Proxy for a single endnote in the document.
Source code in `src/docx/footnotes.py`
```
def __init__(self, note_elm: CT_FtnEdn, notes_part: StoryPart):
super().__init__(note_elm, notes_part)
self._note_elm = note_elm
```
#### endnote_id
```
endnote_id: int
```
The identifier a `w:endnoteReference` uses to cite this endnote.
### Footnotes
```
Footnotes(
notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart
)
```
Bases: `_Notes`
Collection containing the footnotes of this document.
Only the footnotes an author wrote are in the collection. Word keeps two more in the same part, at ids -1 and 0, which hold the rule it draws above the footnote area and its continuation on the next page; those are structural and never appear here.
Source code in `src/docx/footnotes.py`
```
def __init__(self, notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart):
self._notes_elm = notes_elm
self._notes_part = notes_part
```
#### add_footnote
```
add_footnote(text: str = '') -> Footnote
```
Add a new footnote to the document and return it.
The footnote is added to the end of the footnotes collection and is assigned an id unique within it. Adding it does not place a reference to it in the body text; use `Run.add_footnote_reference()` for that. A footnote no run references does not appear in the rendered document.
If `text` is provided it is added to the footnote, after the reference mark that Word renders as the footnote number. Multiple paragraphs can be added by separating their text with newlines (`"\\n"`); between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`.
The default is a footnote holding only the reference mark, to which runs can be added with `footnote.paragraphs[0].add_run()` and further paragraphs with `.add_paragraph()`.
Source code in `src/docx/footnotes.py`
```
def add_footnote(self, text: str = "") -> Footnote:
"""Add a new footnote to the document and return it.
The footnote is added to the end of the footnotes collection and is assigned an
id unique within it. Adding it does not place a reference to it in the body
text; use `Run.add_footnote_reference()` for that. A footnote no run references
does not appear in the rendered document.
If `text` is provided it is added to the footnote, after the reference mark that
Word renders as the footnote number. Multiple paragraphs can be added by
separating their text with newlines (`"\\\\n"`); between newlines, text is
interpreted as it is in `Document.add_paragraph(text=...)`.
The default is a footnote holding only the reference mark, to which runs can be
added with `footnote.paragraphs[0].add_run()` and further paragraphs with
`.add_paragraph()`.
"""
return self._add_note(text) # pyright: ignore[reportReturnType]
```
#### get
```
get(footnote_id: int) -> Footnote | None
```
The footnote identified by `footnote_id`, or `None` if there is none.
`None` is also returned for the ids of Word's structural separator footnotes, which are not footnotes of this document in any sense an author would mean.
Source code in `src/docx/footnotes.py`
```
def get(self, footnote_id: int) -> Footnote | None:
"""The footnote identified by `footnote_id`, or |None| if there is none.
|None| is also returned for the ids of Word's structural separator footnotes,
which are not footnotes of this document in any sense an author would mean.
"""
return self._get(footnote_id) # pyright: ignore[reportReturnType]
```
### Endnotes
```
Endnotes(
notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart
)
```
Bases: `_Notes`
Collection containing the endnotes of this document.
The endnote counterpart of Footnotes, and identical to it in behavior. Where an endnote appears — end of section or end of document — is a document setting, in `w:sectPr/w:endnotePr` and `w:settings/w:endnotePr`, and is not exposed here.
Source code in `src/docx/footnotes.py`
```
def __init__(self, notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart):
self._notes_elm = notes_elm
self._notes_part = notes_part
```
#### add_endnote
```
add_endnote(text: str = '') -> Endnote
```
Add a new endnote to the document and return it.
As for `Footnotes.add_footnote()`: the endnote is given an id unique within the collection, and adding it does not place a reference to it in the body text — use `Run.add_endnote_reference()` for that. An endnote no run references does not appear in the rendered document.
Source code in `src/docx/footnotes.py`
```
def add_endnote(self, text: str = "") -> Endnote:
"""Add a new endnote to the document and return it.
As for `Footnotes.add_footnote()`: the endnote is given an id unique within the
collection, and adding it does not place a reference to it in the body text —
use `Run.add_endnote_reference()` for that. An endnote no run references does
not appear in the rendered document.
"""
return self._add_note(text) # pyright: ignore[reportReturnType]
```
#### get
```
get(endnote_id: int) -> Endnote | None
```
The endnote identified by `endnote_id`, or `None` if there is none.
Source code in `src/docx/footnotes.py`
```
def get(self, endnote_id: int) -> Endnote | None:
"""The endnote identified by `endnote_id`, or |None| if there is none."""
return self._get(endnote_id) # pyright: ignore[reportReturnType]
```
## formfield
The FormField object, a legacy Word form field.
### FormField
```
FormField(fldChar: CT_FldChar, parent: ProvidesStoryPart)
```
Bases: `StoryChild`
A legacy form field — a text input, check box or drop-down.
Word writes a form field as a complex field: a "begin" field-character carrying the field properties in `w:ffData`, the field instruction, a "separate" field-character, the current value, and an "end" field-character, each in its own run. This object proxies the "begin" field-character and reaches the rest through it.
Legacy form fields are what Word's Developer ribbon calls "Legacy Forms". They are distinct from content controls (`w:sdt`), which ContentControl covers.
The whole field is expected to sit in one paragraph, which is how Word writes a legacy form field — it does not let a paragraph break be typed into one. Reading or writing value on a field whose "end" field-character is in a later paragraph raises InvalidXmlError rather than returning a partial value.
Source code in `src/docx/formfield.py`
```
def __init__(self, fldChar: CT_FldChar, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._element = self._fldChar = fldChar
```
#### calc_on_exit
```
calc_on_exit: bool | None
```
Whether Word recalculates its fields when this one is left.
`None` when the document does not say, which Word treats as `False`.
#### default
```
default: str | bool | None
```
The value this field starts out holding, `None` when it has no default.
A `bool` for a check box, the text for a text input, and the selected entry for a drop-down, matching value. Assigning an entry a drop-down does not offer raises `ValueError`, as it does for value; assigning `None` removes the default.
#### enabled
```
enabled: bool | None
```
Whether the field can be edited.
`None` when the document does not say, which Word treats as enabled.
#### help_text
```
help_text: str | None
```
The text Word shows when F1 is pressed in this field, or `None`.
#### items
```
items: tuple[str, ...]
```
The entries of a drop-down field, in the order Word lists them.
Empty for a field that is not a drop-down.
#### max_length
```
max_length: int | None
```
The most characters a text field accepts, `None` when unlimited.
`None` for a field that is not a text input.
#### name
```
name: str | None
```
The bookmark name Word knows this field by, or `None` when it has none.
This is the name shown in the "Bookmark" box of the form-field properties dialog and the one a `REF` field or a macro would use.
#### status_text
```
status_text: str | None
```
The text Word shows in the status bar for this field, or `None`.
#### text_type
```
text_type: WD_TEXT_FORM_FIELD_TYPE | None
```
Member of WdTextFormFieldType a text field accepts, or `None`.
`None` both for a field that is not a text input and for a text input that does not say, which Word treats as `REGULAR_TEXT`.
#### type
```
type: WD_FORM_FIELD_TYPE
```
Member of `WdFormFieldType` telling what kind of field this is.
#### value
```
value: str | bool
```
The value this field currently holds.
A `bool` for a check box; for a drop-down the selected entry, the empty string when nothing is selected; for a text input the result text Word last rendered, which is the empty string for an empty field.
Note that Word renders an empty text field as five spaces or similar filler text; that filler is what this returns, because it is what the document contains. Compare against default to tell an untouched field apart.
### iter_form_fields
```
iter_form_fields(
element: _Element, parent: ProvidesStoryPart
) -> Iterator[FormField]
```
Generate a FormField for each legacy form field in the subtree of `element`.
Source code in `src/docx/formfield.py`
```
def iter_form_fields(element: _Element, parent: t.ProvidesStoryPart) -> Iterator[FormField]:
"""Generate a |FormField| for each legacy form field in the subtree of `element`."""
for fldChar in cast("List[CT_FldChar]", element.xpath(_FORM_FIELD_XPATH)):
yield FormField(fldChar, parent)
```
## math
The Math proxy, giving access to the equations in a document.
### Math
```
Math(oMath: CT_OMath, parent: ProvidesStoryPart)
```
Bases: `StoryChild`
Proxy for an `m:oMath` element, one equation.
Reached through Paragraph.math or Document.math.
Word stores equations in OMML, a notation of its own with no overlap with the wordprocessing run content. This exposes the equation's XML and the characters in it; it deliberately does not model the notation, which would be a substantially larger piece of work with nothing to render the result.
Source code in `src/docx/math.py`
```
def __init__(self, oMath: CT_OMath, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._element = oMath
self._oMath = oMath
```
#### text
```
text: str
```
The characters of this equation, with none of its structure.
A fraction reads as its numerator followed by its denominator, a superscript as its base followed by its exponent. This is what a plain-text extraction can offer; it is not a rendering of the equation and will not round-trip.
#### xml
```
xml: str
```
The OMML source of this equation.
The reliable representation, and what to hand to anything that understands OMML — an XSLT to MathML, say.
#### is_display
```
is_display: bool
```
`True` when this equation is displayed on a line of its own.
Word wraps a display equation in an `m:oMathPara`; an inline one sits directly among the runs of its paragraph.
### iter_math
```
iter_math(
element: BaseOxmlElement, parent: ProvidesStoryPart
) -> Iterator[Math]
```
Generate a Math for each equation under `element`, in document order.
Both the inline `m:oMath` and the `m:oMath` children of a display `m:oMathPara` are found, and each is yielded once.
Source code in `src/docx/math.py`
```
def iter_math(element: BaseOxmlElement, parent: t.ProvidesStoryPart) -> Iterator[Math]:
"""Generate a |Math| for each equation under `element`, in document order.
Both the inline `m:oMath` and the `m:oMath` children of a display `m:oMathPara` are
found, and each is yielded once.
"""
for oMath in element.iter(qn("m:oMath")):
yield Math(oMath, parent) # pyright: ignore[reportArgumentType]
```
### math_list
```
math_list(
element: BaseOxmlElement, parent: ProvidesStoryPart
) -> List[Math]
```
The equations under `element`, in document order.
Source code in `src/docx/math.py`
```
def math_list(element: BaseOxmlElement, parent: t.ProvidesStoryPart) -> List[Math]:
"""The equations under `element`, in document order."""
return list(iter_math(element, parent))
```
## numbering
The list-numbering API — reading the number a list paragraph displays, and restarting.
The number a reader sees against a list paragraph — "1.", "a)", "iii." — is nowhere in the document body. Word computes it from `numbering.xml` at display time, so anything converting a document to text, Markdown or HTML has to compute it too. That computation is compute_list_numbers, and it is the part of this module worth being careful about; the rest is a straightforward model over the numbering part.
The model mirrors the two-level indirection described in `docx.oxml.numbering`: a NumberingDefinition is a `w:num`, a concrete list, and it resolves each of its nine NumberingLevel objects from the `w:abstractNum` it points at, with any `w:lvlOverride` of its own applied on top.
Restarting a list means creating a second `w:num` on the same `w:abstractNum` carrying a `w:startOverride`, not resetting a counter; see Paragraph.restart_numbering.
What is not computed here: a level whose format is one of the locale-specific ones — Japanese counting, Korean chosung and the rest — falls back to decimal, because rendering those correctly is a localisation problem rather than a document-model one. NumberingLevel.is_renderable says which is which.
### NumberingLevel
```
NumberingLevel(
ilvl: int,
lvl: CT_Lvl | None,
start_override: int | None = None,
)
```
One of the nine levels of a list, with any instance overrides applied.
Reached through NumberingDefinition.levels or NumberingDefinition.level.
Source code in `src/docx/numbering.py`
```
def __init__(self, ilvl: int, lvl: CT_Lvl | None, start_override: int | None = None):
self._ilvl = ilvl
self._lvl = lvl
self._start_override = start_override
```
#### ilvl
```
ilvl: int
```
The zero-based level number, 0 being the outermost.
#### is_bullet
```
is_bullet: bool
```
True when this level shows a bullet rather than a number.
#### is_renderable
```
is_renderable: bool
```
True when format_number can render this level's format faithfully.
`False` for the locale-specific formats, where `format_number` falls back to decimal. Worth checking before presenting a computed number as authoritative.
#### level_text
```
level_text: str
```
The pattern this level displays, e.g. `"%1."`.
A `%n` is the counter of one-based level `n`. Defaults to `"%{ilvl+1}."`, which is what Word shows for a level that does not say.
#### number_format
```
number_format: WD_NUMBER_FORMAT
```
Member of `WdNumberFormat` this level renders its counter as.
`DECIMAL` when the level does not say, which is Word's default.
#### restart_after_level
```
restart_after_level: int | None
```
The one-based level whose increment restarts this one.
`None` means Word's default: restart whenever any higher level increments. 0 means never restart.
#### start
```
start: int
```
The number this level counts from.
A `w:startOverride` on the concrete list wins over the abstract definition's `w:start`, which is how a restarted list begins again at 1. Defaults to 1.
#### style_id
```
style_id: str | None
```
The paragraph style linked to this level, or `None`.
A paragraph with this style takes this level even with no `w:numPr` of its own.
#### is_legal
```
is_legal: bool
```
True when this level renders every placeholder as decimal.
Word's "legal numbering" option, which turns "1.a.i" into "1.1.1".
#### suffix
```
suffix: str
```
What separates the number from the text: `"tab"`, `"space"` or `"nothing"`.
`"tab"` when the level does not say, which is Word's default.
#### indent
```
indent: Length | None
```
The left indent this level applies, or `None` when it sets none.
#### hanging_indent
```
hanging_indent: Length | None
```
The hanging indent this level applies, or `None` when it sets none.
This is what keeps the wrapped text of a list item lined up under the first line rather than under the bullet.
#### set
```
set(
*,
start: int | None = None,
number_format: WD_NUMBER_FORMAT | str | None = None,
level_text: str | None = None,
suffix: str | None = None,
alignment: str | None = None,
indent: Length | None = None,
hanging_indent: Length | None = None,
restart_after_level: int | None = None,
style_id: str | None = None,
is_legal: bool | None = None,
) -> NumberingLevel
```
Change this level's definition; return self for chaining.
Only the arguments given are written, so a call sets what it names and leaves the rest of the level alone:
```
level.set(number_format=WD_NUMBER_FORMAT.LOWER_LETTER, level_text="%2)")
```
The change is made to the *abstract* definition, which is shared: every list pointing at it changes with it. Use Numbering.add_definition for a list of your own rather than editing a definition the document already had.
Raises `ValueError` for a level that has no definition to write to — one of the nine levels an abstract definition does not define.
Source code in `src/docx/numbering.py`
```
def set(
self,
*,
start: int | None = None,
number_format: WD_NUMBER_FORMAT | str | None = None,
level_text: str | None = None,
suffix: str | None = None,
alignment: str | None = None,
indent: Length | None = None,
hanging_indent: Length | None = None,
restart_after_level: int | None = None,
style_id: str | None = None,
is_legal: bool | None = None,
) -> NumberingLevel:
"""Change this level's definition; return self for chaining.
Only the arguments given are written, so a call sets what it names and leaves
the rest of the level alone::
level.set(number_format=WD_NUMBER_FORMAT.LOWER_LETTER, level_text="%2)")
The change is made to the *abstract* definition, which is shared: every list
pointing at it changes with it. Use :meth:`.Numbering.add_definition` for a list
of your own rather than editing a definition the document already had.
Raises |ValueError| for a level that has no definition to write to — one of the
nine levels an abstract definition does not define.
"""
if self._lvl is None:
raise ValueError(
"level %d has no definition in this list; only levels the abstract"
" definition defines can be changed" % self._ilvl
)
lvl = self._lvl
if start is not None:
lvl.start = start
if number_format is not None:
lvl.num_fmt = number_format
if level_text is not None:
lvl.lvl_text = level_text
if suffix is not None:
lvl.suffix = suffix
if alignment is not None:
lvl.jc = alignment
if restart_after_level is not None:
lvl.lvl_restart = restart_after_level
if style_id is not None:
lvl.p_style = style_id
if is_legal is not None:
lvl.is_lgl = is_legal
if indent is not None or hanging_indent is not None:
pPr = lvl.get_or_add_pPr()
if indent is not None:
pPr.ind_left = indent # pyright: ignore[reportAttributeAccessIssue]
if hanging_indent is not None:
# -- a hanging indent is a negative first-line indent, which is how
# -- `CT_PPr.first_line_indent` already spells it --
pPr.first_line_indent = Length( # pyright: ignore[reportAttributeAccessIssue]
-hanging_indent
)
return self
```
#### format_number
```
format_number(value: int) -> str
```
`value` rendered in this level's number format.
Falls back to decimal for a format this library does not render; see is_renderable.
Source code in `src/docx/numbering.py`
```
def format_number(self, value: int) -> str:
"""`value` rendered in this level's number format.
Falls back to decimal for a format this library does not render; see
:attr:`is_renderable`.
"""
fmt = self.number_format
if fmt == WD_NUMBER_FORMAT.UPPER_ROMAN:
return _to_roman(value)
if fmt == WD_NUMBER_FORMAT.LOWER_ROMAN:
return _to_roman(value).lower()
if fmt == WD_NUMBER_FORMAT.UPPER_LETTER:
return _to_letter(value)
if fmt == WD_NUMBER_FORMAT.LOWER_LETTER:
return _to_letter(value).lower()
if fmt == WD_NUMBER_FORMAT.ORDINAL:
return _to_ordinal(value)
if fmt == WD_NUMBER_FORMAT.HEX:
return format(value, "X")
if fmt == WD_NUMBER_FORMAT.DECIMAL_ZERO:
return f"{value:02d}"
if fmt == WD_NUMBER_FORMAT.CHICAGO:
return _CHICAGO[(value - 1) % len(_CHICAGO)] if value > 0 else str(value)
if fmt == WD_NUMBER_FORMAT.NONE:
return ""
return str(value)
```
### NumberingDefinition
```
NumberingDefinition(num: CT_Num, numbering: Numbering)
```
A concrete list — a `w:num` — and the levels it resolves to.
Two definitions pointing at the same abstract definition are two independent sequences that happen to look alike; that is how a restarted list is represented.
Source code in `src/docx/numbering.py`
```
def __init__(self, num: CT_Num, numbering: Numbering):
self._num = num
self._numbering = numbering
```
#### abstract_num_id
```
abstract_num_id: int | None
```
The id of the abstract definition this list takes its formatting from.
#### num_id
```
num_id: int
```
The id a paragraph's `w:numPr/w:numId` refers to this list by.
#### levels
```
levels: List[NumberingLevel]
```
The nine levels of this list, outermost first.
#### level
```
level(ilvl: int) -> NumberingLevel
```
The level `ilvl` of this list, with any instance override applied.
A level the definition says nothing about is still returned, carrying Word's defaults, because a paragraph can legitimately refer to it.
Source code in `src/docx/numbering.py`
```
def level(self, ilvl: int) -> NumberingLevel:
"""The level `ilvl` of this list, with any instance override applied.
A level the definition says nothing about is still returned, carrying Word's
defaults, because a paragraph can legitimately refer to it.
"""
abstract = self._abstract_num
lvl = None if abstract is None else abstract.lvl_having_ilvl(ilvl)
start_override = None
lvlOverride = self._num.lvlOverride_having_ilvl(ilvl)
if lvlOverride is not None:
start_override = lvlOverride.start_override
if lvlOverride.lvl is not None:
lvl = lvlOverride.lvl
return NumberingLevel(ilvl, lvl, start_override)
```
### Numbering
```
Numbering(numbering: CT_Numbering, part: DocumentPart)
```
Bases: `ElementProxy`
The numbering definitions of a document.
Reached through Document.numbering. Supports `len()`, iteration over the concrete list definitions, and lookup by `num_id`.
Source code in `src/docx/numbering.py`
```
def __init__(self, numbering: CT_Numbering, part: DocumentPart):
super().__init__(numbering)
self._element = numbering
self._part = part
```
#### get
```
get(num_id: int) -> NumberingDefinition | None
```
The list definition with `num_id`, or `None` when there is none.
Source code in `src/docx/numbering.py`
```
def get(self, num_id: int) -> NumberingDefinition | None:
"""The list definition with `num_id`, or |None| when there is none."""
try:
num = self._element.num_having_numId(num_id)
except KeyError:
return None
return NumberingDefinition(num, self)
```
#### add_definition
```
add_definition(
levels: Sequence[Mapping[str, object]] | None = None,
*,
multi_level_type: str | None = None,
) -> NumberingDefinition
```
Define a new list and return it.
Until now a list could be *applied* and *restarted* but not defined, so a format the template did not already contain — `1)` where the template has `1.`, a custom bullet character, a particular per-level indent — meant hand-building `w:abstractNum` XML:
```
definition = document.numbering.add_definition([
{"number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%1)"},
{"number_format": WD_NUMBER_FORMAT.LOWER_LETTER, "level_text": "%2)"},
])
paragraph.set_numbering(definition.num_id, level=0)
```
`levels` is one mapping per level, outermost first, of the keyword arguments NumberingLevel.set takes. A level given as an empty mapping takes the defaults. With `levels` of `None` the definition gets nine decimal levels, which is what Word's plain numbered list is; add_bulleted_definition and add_numbered_definition are the shorthands for the two common cases.
`multi_level_type` is written to `w:multiLevelType` when given; Word uses it to decide how to present the list in its gallery and is content without it.
A fresh `w:abstractNum` and a `w:num` pointing at it are created, both with ids free in this document. `w:nsid` and `w:tmpl` are deliberately not written — they are what Word uses to recognise a definition as one of its own gallery entries, and inventing values would claim a provenance this definition does not have.
Raises `ValueError` for more than nine levels, which is all OOXML admits.
Source code in `src/docx/numbering.py`
```
def add_definition(
self,
levels: Sequence[Mapping[str, object]] | None = None,
*,
multi_level_type: str | None = None,
) -> NumberingDefinition:
"""Define a new list and return it.
Until now a list could be *applied* and *restarted* but not defined, so a format
the template did not already contain — `1)` where the template has `1.`, a
custom bullet character, a particular per-level indent — meant hand-building
`w:abstractNum` XML::
definition = document.numbering.add_definition([
{"number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%1)"},
{"number_format": WD_NUMBER_FORMAT.LOWER_LETTER, "level_text": "%2)"},
])
paragraph.set_numbering(definition.num_id, level=0)
`levels` is one mapping per level, outermost first, of the keyword arguments
:meth:`.NumberingLevel.set` takes. A level given as an empty mapping takes the
defaults. With `levels` of |None| the definition gets nine decimal levels, which
is what Word's plain numbered list is; :meth:`add_bulleted_definition` and
:meth:`add_numbered_definition` are the shorthands for the two common cases.
`multi_level_type` is written to `w:multiLevelType` when given; Word uses it to
decide how to present the list in its gallery and is content without it.
A fresh `w:abstractNum` and a `w:num` pointing at it are created, both with ids
free in this document. `w:nsid` and `w:tmpl` are deliberately not written — they
are what Word uses to recognise a definition as one of its own gallery entries,
and inventing values would claim a provenance this definition does not have.
Raises |ValueError| for more than nine levels, which is all OOXML admits.
"""
from docx.oxml.numbering import CT_AbstractNum
if levels is None:
levels = [{} for _ in range(_MAX_LEVELS)]
elif len(levels) > _MAX_LEVELS:
raise ValueError(
"a list definition has at most %d levels, got %d"
% (_MAX_LEVELS, len(levels))
)
abstract = CT_AbstractNum.new(self._next_abstract_num_id())
self._insert_abstract_num(abstract)
if multi_level_type is not None:
abstract.multi_level_type = multi_level_type
for ilvl, spec in enumerate(levels):
lvl = abstract.add_level(ilvl)
NumberingLevel(ilvl, lvl).set(
**{
"number_format": WD_NUMBER_FORMAT.DECIMAL,
"level_text": "%%%d." % (ilvl + 1),
"start": 1,
**spec, # pyright: ignore[reportArgumentType]
}
)
num = self._element.add_num(abstract.abstractNumId)
return NumberingDefinition(num, self)
```
#### add_numbered_definition
```
add_numbered_definition(
depth: int = 9,
*,
formats: Sequence[WD_NUMBER_FORMAT] | None = None,
indent_step: Length | None = None,
) -> NumberingDefinition
```
Define a decimal-with-sublevels list and return it.
The tedious half of add_definition is assembling nine levels by hand, so this does it: each level shows its own counter followed by a period, in the format `formats` gives it, indented `indent_step` further than the level above.
`formats` cycles when it is shorter than `depth`; the default of decimal, lower letter and lower roman is Word's familiar 1. / a. / i. alternation. For the cumulative "1.1.1" style, pass `levels` to add_definition with `level_text` of `"%1.%2."` and so on. `indent_step` defaults to a quarter inch, which is what Word uses.
Raises `ValueError` for a `depth` above nine, as add_definition does.
Source code in `src/docx/numbering.py`
```
def add_numbered_definition(
self,
depth: int = 9,
*,
formats: Sequence[WD_NUMBER_FORMAT] | None = None,
indent_step: Length | None = None,
) -> NumberingDefinition:
"""Define a decimal-with-sublevels list and return it.
The tedious half of :meth:`add_definition` is assembling nine levels by hand, so
this does it: each level shows its own counter followed by a period, in the
format `formats` gives it, indented `indent_step` further than the level above.
`formats` cycles when it is shorter than `depth`; the default of decimal, lower
letter and lower roman is Word's familiar 1. / a. / i. alternation. For the
cumulative "1.1.1" style, pass `levels` to :meth:`add_definition` with
`level_text` of ``"%1.%2."`` and so on. `indent_step` defaults to a quarter
inch, which is what Word uses.
Raises |ValueError| for a `depth` above nine, as :meth:`add_definition` does.
"""
from docx.shared import Inches
if formats is None:
formats = (
WD_NUMBER_FORMAT.DECIMAL,
WD_NUMBER_FORMAT.LOWER_LETTER,
WD_NUMBER_FORMAT.LOWER_ROMAN,
)
step = Inches(0.25) if indent_step is None else indent_step
levels: List[Dict[str, object]] = []
for ilvl in range(depth):
levels.append(
{
"number_format": formats[ilvl % len(formats)],
"level_text": "%%%d." % (ilvl + 1),
"start": 1,
"indent": Length(step * (ilvl + 2)),
"hanging_indent": step,
}
)
return self.add_definition(levels, multi_level_type="multilevel")
```
#### add_bulleted_definition
```
add_bulleted_definition(
depth: int = 9,
*,
bullets: Sequence[str] = ("•", "o", "§"),
indent_step: Length | None = None,
) -> NumberingDefinition
```
Define a bulleted list and return it.
`bullets` cycles when it is shorter than `depth`; the default is Word's own bullet, circle and square sequence. `indent_step` defaults to a quarter inch. Raises `ValueError` for a `depth` above nine.
Note the bullet characters Word writes are glyphs of the Symbol and Wingdings fonts rather than the Unicode characters they resemble. The defaults here are the Unicode ones, which render in whatever font the paragraph uses and so do not depend on a font being installed.
Source code in `src/docx/numbering.py`
```
def add_bulleted_definition(
self,
depth: int = 9,
*,
bullets: Sequence[str] = ("•", "o", "§"),
indent_step: Length | None = None,
) -> NumberingDefinition:
"""Define a bulleted list and return it.
`bullets` cycles when it is shorter than `depth`; the default is Word's own
bullet, circle and square sequence. `indent_step` defaults to a quarter inch.
Raises |ValueError| for a `depth` above nine.
Note the bullet characters Word writes are glyphs of the Symbol and Wingdings
fonts rather than the Unicode characters they resemble. The defaults here are
the Unicode ones, which render in whatever font the paragraph uses and so do not
depend on a font being installed.
"""
from docx.shared import Inches
step = Inches(0.25) if indent_step is None else indent_step
levels: List[Dict[str, object]] = []
for ilvl in range(depth):
levels.append(
{
"number_format": WD_NUMBER_FORMAT.BULLET,
"level_text": bullets[ilvl % len(bullets)],
"indent": Length(step * (ilvl + 2)),
"hanging_indent": step,
}
)
return self.add_definition(levels, multi_level_type="hybridMultilevel")
```
#### restart
```
restart(
num_id: int, ilvl: int = 0, start: int = 1
) -> NumberingDefinition
```
Return a new list definition restarting the list `num_id` at `start`.
The new definition points at the same abstract definition, so it looks identical, and carries a `w:startOverride` for level `ilvl`. Assign its num_id to a paragraph to make the list begin again there; Paragraph.restart_numbering does that in one step.
Raises `KeyError` if `num_id` names no list.
Source code in `src/docx/numbering.py`
```
def restart(self, num_id: int, ilvl: int = 0, start: int = 1) -> NumberingDefinition:
"""Return a new list definition restarting the list `num_id` at `start`.
The new definition points at the same abstract definition, so it looks identical,
and carries a `w:startOverride` for level `ilvl`. Assign its :attr:`num_id` to a
paragraph to make the list begin again there; :meth:`.Paragraph.restart_numbering`
does that in one step.
Raises |KeyError| if `num_id` names no list.
"""
source = self.get(num_id)
if source is None:
raise KeyError(f"no numbering definition with num_id {num_id}")
abstract_num_id = source.abstract_num_id
if abstract_num_id is None:
raise KeyError(f"numbering definition {num_id} names no abstract definition")
num = self._element.add_num(abstract_num_id)
num.add_lvlOverride(ilvl).add_startOverride(start)
return NumberingDefinition(num, self)
```
### ParagraphNumbering
```
ParagraphNumbering(
num_id: int,
level: int,
numbering: Numbering,
from_style: bool,
)
```
The list membership of a paragraph — which list it is in, and at what level.
Reached through Paragraph.numbering, which is `None` for a paragraph that is not in a list at all.
Source code in `src/docx/numbering.py`
```
def __init__(self, num_id: int, level: int, numbering: Numbering, from_style: bool):
self._num_id = num_id
self._level = level
self._numbering = numbering
self._from_style = from_style
```
#### definition
```
definition: NumberingDefinition | None
```
The list this paragraph belongs to, `None` if `num_id` names none.
#### from_style
```
from_style: bool
```
True when this numbering comes from the paragraph's style, not the paragraph.
A paragraph numbered through its style has no `w:numPr` of its own, so changing its level means giving it one.
#### level
```
level: int
```
The zero-based list level of this paragraph, 0 being the outermost.
#### level_definition
```
level_definition: NumberingLevel | None
```
The NumberingLevel governing this paragraph, `None` if unresolvable.
#### num_id
```
num_id: int
```
The id of the list this paragraph belongs to.
### get_paragraph_numbering
```
get_paragraph_numbering(
p: CT_P, part: DocumentPart
) -> Tuple[int, int, bool] | None
```
`(num_id, ilvl, from_style)` for `p`, or `None` when it is not in a list.
A direct `w:pPr/w:numPr` on the paragraph wins. Failing that the paragraph's style hierarchy is walked — a style's own `w:numPr`, then the style it is based on — which is how the built-in "List Number" and "List Bullet" styles number a paragraph that carries no numbering markup at all.
`w:numId` of 0 means "explicitly not numbered" and is honoured as such: Word uses it to switch numbering off for a paragraph whose style would otherwise apply it.
Source code in `src/docx/numbering.py`
```
def get_paragraph_numbering(p: CT_P, part: DocumentPart) -> Tuple[int, int, bool] | None:
"""`(num_id, ilvl, from_style)` for `p`, or |None| when it is not in a list.
A direct `w:pPr/w:numPr` on the paragraph wins. Failing that the paragraph's style
hierarchy is walked — a style's own `w:numPr`, then the style it is based on — which
is how the built-in "List Number" and "List Bullet" styles number a paragraph that
carries no numbering markup at all.
`w:numId` of 0 means "explicitly not numbered" and is honoured as such: Word uses it
to switch numbering off for a paragraph whose style would otherwise apply it.
"""
pPr = p.pPr
if pPr is not None:
numPr = pPr.numPr
if numPr is not None:
num_id = numPr.numId_val
if num_id is not None:
if num_id == 0:
return None
return num_id, numPr.ilvl_val or 0, False
style_id = p.style
if style_id is None:
return None
resolved = _numbering_from_style(style_id, part)
if resolved is None:
return None
num_id, ilvl = resolved
if num_id == 0:
return None
return num_id, ilvl, True
```
### compute_list_numbers
```
compute_list_numbers(
paragraphs: Iterator[CT_P], part: DocumentPart
) -> List[Tuple[CT_P, str]]
```
`(paragraph_element, number)` for each numbered paragraph, in document order.
`paragraphs` must be every paragraph of the story in document order, since a list number depends on everything before it. Paragraphs that are not in a list contribute no entry.
The elements are returned rather than used as dictionary keys because an lxml element proxy is created on demand and may be collected and its `id()` reused; holding the element in the result is what keeps the association valid.
The counters follow ISO/IEC 29500 §17.9. For each numbered paragraph the counter of its own level increments, and every deeper level is restarted — unless its `w:lvlRestart` says otherwise, where 0 means never restart and *n* means restart only when the one-based level *n* increments. Counters are kept per `w:num` rather than per abstract definition, which is what makes two lists sharing a definition count independently and what makes a `w:startOverride` restart work.
Source code in `src/docx/numbering.py`
```
def compute_list_numbers(paragraphs: Iterator[CT_P], part: DocumentPart) -> List[Tuple[CT_P, str]]:
"""`(paragraph_element, number)` for each numbered paragraph, in document order.
`paragraphs` must be every paragraph of the story in document order, since a list
number depends on everything before it. Paragraphs that are not in a list contribute
no entry.
The elements are returned rather than used as dictionary keys because an lxml element
proxy is created on demand and may be collected and its `id()` reused; holding the
element in the result is what keeps the association valid.
The counters follow ISO/IEC 29500 §17.9. For each numbered paragraph the counter of
its own level increments, and every deeper level is restarted — unless its
`w:lvlRestart` says otherwise, where 0 means never restart and *n* means restart only
when the one-based level *n* increments. Counters are kept per `w:num` rather than
per abstract definition, which is what makes two lists sharing a definition count
independently and what makes a `w:startOverride` restart work.
"""
if not part.has_numbering_part:
# -- no numbering part means no list can resolve; say so without adding one --
return []
numbering = Numbering(part.numbering_part.element, part)
# -- counters[num_id][ilvl] is the last number shown at that level. A level absent
# -- from the dict has not started yet and takes its `start` value next. --
counters: Dict[int, Dict[int, int]] = {}
numbers: List[Tuple[CT_P, str]] = []
for p in paragraphs:
resolved = get_paragraph_numbering(p, part)
if resolved is None:
continue
num_id, ilvl, _ = resolved
definition = numbering.get(num_id)
if definition is None:
continue
level = definition.level(ilvl)
list_counters = counters.setdefault(num_id, {})
if ilvl in list_counters:
list_counters[ilvl] += 1
else:
list_counters[ilvl] = level.start
_restart_deeper_levels(definition, list_counters, ilvl)
numbers.append((p, _render(definition, level, list_counters, ilvl)))
return numbers
```
### iter_story_paragraphs
```
iter_story_paragraphs(element) -> Iterator[CT_P]
```
Generate every `w:p` in `element` in document order, tables included.
List numbering counts paragraphs in the order Word lays them out, and a numbered paragraph inside a table cell counts towards the same list as one outside it.
Source code in `src/docx/numbering.py`
```
def iter_story_paragraphs(element) -> Iterator[CT_P]:
"""Generate every `w:p` in `element` in document order, tables included.
List numbering counts paragraphs in the order Word lays them out, and a numbered
paragraph inside a table cell counts towards the same list as one outside it.
"""
for p in element.iter(qn("w:p")):
yield p
```
## object
The EmbeddedObject proxy — a file embedded in a document as an OLE object.
Word can embed a whole file inside a document and show it as an icon or a preview image that opens the original application on double-click. The read side matters on its own: for a document containing embedded attachments there was previously no way to discover that they exist, let alone extract them.
This is a different thing from the two neighbouring features. `Document.add_alt_chunk()` imports content and dissolves it into the document when Word opens the file; an OLE object stays a distinct embedded file. `Run.add_picture()` embeds an image with no underlying document.
### EmbeddedObject
```
EmbeddedObject(
object_elm: CT_Object, parent: ProvidesStoryPart
)
```
Bases: `StoryChild`
An OLE object embedded in a run — a spreadsheet, a PDF, another document.
Reached through Run.embedded_objects or Document.embedded_objects.
Source code in `src/docx/object.py`
```
def __init__(self, object_elm: CT_Object, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._element = object_elm
self._object = object_elm
```
#### prog_id
```
prog_id: str | None
```
The application Word launches for this object, e.g. `"Excel.Sheet.12"`.
`None` when the object names none. This is what tells Word which application to open; an object whose `ProgID` names nothing installed is one Word shows but cannot open.
#### is_linked
```
is_linked: bool
```
`True` when the object links to an external file rather than embedding it.
A linked object's bytes are not in the package, so blob is `None`.
#### shows_icon
```
shows_icon: bool
```
`True` when Word shows this object as an icon rather than a preview.
#### embedded_part
```
embedded_part: Part | None
```
The package part holding the embedded file, or `None`.
`None` for a linked object, and for an embedded one whose relationship the document does not resolve — which is a broken document rather than an error here.
#### blob
```
blob: bytes | None
```
The bytes of the embedded file, or `None` when there are none to give.
This is the useful half — extracting an attachment from a document:
```
for obj in document.embedded_objects:
if obj.blob is not None:
Path(obj.filename or "attachment").write_bytes(obj.blob)
```
#### content_type
```
content_type: str | None
```
The content type of the embedded part, or `None` when there is no part.
#### filename
```
filename: str | None
```
The partname's basename, e.g. `"oleObject1.bin"`, or `None`.
OOXML does not record the original file name of an embedded object; this is the name of the part it landed in, which is what a caller extracting it has to work with.
#### image
```
image: Image | None
```
The icon or preview image Word displays for this object, or `None`.
Every OLE object has one — Word cannot render the embedded file itself — so `None` means the document is missing it rather than that the object has none.
### add_embedded_object
```
add_embedded_object(
run: object,
path_or_stream: str | PathLike[str] | IO[bytes],
*,
icon: str | PathLike[str] | IO[bytes],
prog_id: str | None = None,
width: Length | None = None,
height: Length | None = None,
) -> EmbeddedObject
```
Embed `path_or_stream` in `run` as an OLE object; see Run.add_embedded_object.
Source code in `src/docx/object.py`
```
def add_embedded_object(
run: object,
path_or_stream: str | os.PathLike[str] | IO[bytes],
*,
icon: str | os.PathLike[str] | IO[bytes],
prog_id: str | None = None,
width: Length | None = None,
height: Length | None = None,
) -> EmbeddedObject:
"""Embed `path_or_stream` in `run` as an OLE object; see :meth:`.Run.add_embedded_object`."""
part = run.part # pyright: ignore[reportAttributeAccessIssue]
package = part.package
assert package is not None
blob = _read_blob(path_or_stream)
partname = package.next_partname("/word/embeddings/oleObject%d.bin")
object_part = Part(partname, CT.OFC_OLE_OBJECT, blob, package)
object_rId = part.relate_to(object_part, RT.OLE_OBJECT)
icon_rId, icon_image = part.get_or_add_image(icon)
cx = width if width is not None else icon_image.width
cy = height if height is not None else icon_image.height
ordinal = _next_shape_ordinal(part)
# -- the shape id must be unique in the document, and `o:OLEObject/@ShapeID` names
# -- it, so the two are generated together --
shape_id = "_x0000_i%04d" % ordinal
object_elm = parse_xml(
"\n"
' \n'
' \n'
" \n"
' \n'
""
% (
nsdecls("w", "v", "o", "r"),
shape_id,
Emu(cx).pt,
Emu(cy).pt,
icon_rId,
prog_id or _DEFAULT_PROG_ID,
shape_id,
ordinal,
object_rId,
)
)
run._r.append(object_elm) # pyright: ignore[reportPrivateUsage]
return EmbeddedObject(object_elm, run) # pyright: ignore[reportArgumentType]
```
### iter_embedded_objects
```
iter_embedded_objects(
element: object, parent: ProvidesStoryPart
) -> list[EmbeddedObject]
```
The EmbeddedObject instances under `element`, in document order.
Source code in `src/docx/object.py`
```
def iter_embedded_objects(
element: object, parent: t.ProvidesStoryPart
) -> list[EmbeddedObject]:
"""The |EmbeddedObject| instances under `element`, in document order."""
return [
EmbeddedObject(obj, parent)
for obj in element.xpath(".//w:object") # pyright: ignore[reportAttributeAccessIssue]
]
```
## package
WordprocessingML Package class and related objects.
### Package
Bases: `OpcPackage`
Customizations specific to a WordprocessingML package.
#### after_unmarshal
```
after_unmarshal()
```
Called by loading code after all parts and relationships have been loaded.
This method affords the opportunity for any required post-processing.
Source code in `src/docx/package.py`
```
def after_unmarshal(self):
"""Called by loading code after all parts and relationships have been loaded.
This method affords the opportunity for any required post-processing.
"""
self._gather_image_parts()
```
#### get_or_add_image_part
```
get_or_add_image_part(
image_descriptor: str | PathLike[str] | IO[bytes],
) -> ImagePart
```
Return ImagePart containing image specified by `image_descriptor`.
The image-part is newly created if a matching one is not already present in the collection.
Source code in `src/docx/package.py`
```
def get_or_add_image_part(
self, image_descriptor: str | os.PathLike[str] | IO[bytes]
) -> ImagePart:
"""Return |ImagePart| containing image specified by `image_descriptor`.
The image-part is newly created if a matching one is not already present in the
collection.
"""
return self.image_parts.get_or_add_image_part(image_descriptor)
```
#### image_parts
```
image_parts() -> ImageParts
```
ImageParts collection object for this package.
Source code in `src/docx/package.py`
```
@lazyproperty
def image_parts(self) -> ImageParts:
"""|ImageParts| collection object for this package."""
return ImageParts()
```
### ImageParts
```
ImageParts()
```
Collection of ImagePart objects corresponding to images in the package.
Source code in `src/docx/package.py`
```
def __init__(self):
self._image_parts: list[ImagePart] = []
```
#### get_or_add_image_part
```
get_or_add_image_part(
image_descriptor: str | PathLike[str] | IO[bytes],
) -> ImagePart
```
Return ImagePart object containing image identified by `image_descriptor`.
The image-part is newly created if a matching one is not present in the collection.
Source code in `src/docx/package.py`
```
def get_or_add_image_part(
self, image_descriptor: str | os.PathLike[str] | IO[bytes]
) -> ImagePart:
"""Return |ImagePart| object containing image identified by `image_descriptor`.
The image-part is newly created if a matching one is not present in the
collection.
"""
image = Image.from_file(image_descriptor)
matching_image_part = self._get_by_sha1(image.sha1)
if matching_image_part is not None:
return matching_image_part
return self._add_image_part(image)
```
## revisions
The tracked-changes API — reading, accepting and rejecting revisions.
Any document that has been through review carries revision markup, and before this existed the library's handling of it was silently wrong rather than loudly broken: inserted text was dropped, deleted text was dropped, and `Paragraph.text` returned something that matched neither the original nor the final version of the document.
The text model is now defined:
- Paragraph.text is the document **as it now reads** — every revision accepted. Insertions are included; deletions are not. This is what almost every caller wants and what makes `.text` consistent with what a reader sees with markup hidden.
- Paragraph.original_text is the document **as it read before** the revisions. Deletions are included; insertions are not.
Accepting a revision makes the first reading permanent; rejecting it makes the second.
**In scope:** `w:ins`, `w:del`, `w:moveFrom` and `w:moveTo`, whether they wrap content, mark a paragraph mark as inserted or deleted (which is how a paragraph split or merge is tracked), or mark a table row; and the `*Change` elements recording a formatting change.
**Not in scope:** `w:numberingChange`, and the cell-level merge revisions (`w:cellMerge`). Both are rare and neither has a well-defined accept that this library could perform without guessing.
### Revision
```
Revision(
element: CT_TrackChange, parent: ProvidesStoryPart
)
```
Bases: `StoryChild`
One tracked change — an insertion, a deletion, a move or a formatting change.
Not constructed directly; reached through Document.revisions or Paragraph.revisions.
Source code in `src/docx/revisions.py`
```
def __init__(self, element: CT_TrackChange, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._element = element
```
#### author
```
author: str
```
The name of whoever made this change.
The empty string when the document does not say, which is the case for a `w:tblGridChange` and for a document stripped of personal information.
#### date
```
date: datetime | None
```
When the change was made, `None` when the document does not say.
`None` too for an unparseable timestamp, which an anonymised document has.
#### id
```
id: int | None
```
The `w:id` of this revision, `None` when it carries none.
Unique among the revisions of a document when present.
#### is_paragraph_mark
```
is_paragraph_mark: bool
```
True when this revision is of a paragraph mark rather than of content.
An inserted paragraph mark is a paragraph split; a deleted one is a merge with the paragraph that follows. Accepting or rejecting one therefore joins or splits paragraphs rather than adding or removing text.
#### is_row
```
is_row: bool
```
True when this revision marks a whole table row as inserted or deleted.
#### text
```
text: str
```
The text this revision covers, the empty string when it covers none.
Empty for a paragraph-mark revision, a row revision and a formatting change, none of which cover text of their own.
#### type
```
type: WD_REVISION_TYPE
```
Member of WdRevisionType saying what kind of change this is.
#### accept
```
accept() -> None
```
Keep this change, and remove the record of it.
An insertion's content stays and stops being marked as new; a deletion's content goes; a formatting change's record goes, leaving the current formatting in place. Accepting a deleted paragraph mark merges the paragraph with the one after it, which is what the deletion recorded.
Source code in `src/docx/revisions.py`
```
def accept(self) -> None:
"""Keep this change, and remove the record of it.
An insertion's content stays and stops being marked as new; a deletion's content
goes; a formatting change's record goes, leaving the current formatting in
place. Accepting a deleted paragraph mark merges the paragraph with the one
after it, which is what the deletion recorded.
"""
self._apply(accept=True)
```
#### reject
```
reject() -> None
```
Undo this change, and remove the record of it.
An insertion's content goes; a deletion's content comes back, its `w:delText` turned back into `w:t`; a formatting change puts the recorded previous properties back. Rejecting an inserted paragraph mark merges the paragraph with the one after it, undoing the split.
Source code in `src/docx/revisions.py`
```
def reject(self) -> None:
"""Undo this change, and remove the record of it.
An insertion's content goes; a deletion's content comes back, its `w:delText`
turned back into `w:t`; a formatting change puts the recorded previous
properties back. Rejecting an inserted paragraph mark merges the paragraph with
the one after it, undoing the split.
"""
self._apply(accept=False)
```
### iter_revisions
```
iter_revisions(
element: BaseOxmlElement, parent: ProvidesStoryPart
) -> Iterator[Revision]
```
Generate a Revision for each tracked change in the subtree of `element`.
Source code in `src/docx/revisions.py`
```
def iter_revisions(element: BaseOxmlElement, parent: t.ProvidesStoryPart) -> Iterator[Revision]:
"""Generate a |Revision| for each tracked change in the subtree of `element`."""
for revision_elm in iter_revision_elements(element):
yield Revision(revision_elm, parent)
```
### apply_all
```
apply_all(
element: BaseOxmlElement,
parent: ProvidesStoryPart,
accept: bool,
) -> int
```
Accept or reject every revision in `element`, returning how many were applied.
Applied innermost-last and in reverse document order, so that unwrapping or removing one revision cannot invalidate another that has not been reached yet — a nested revision is dealt with before the one containing it.
Source code in `src/docx/revisions.py`
```
def apply_all(element: BaseOxmlElement, parent: t.ProvidesStoryPart, accept: bool) -> int:
"""Accept or reject every revision in `element`, returning how many were applied.
Applied innermost-last and in reverse document order, so that unwrapping or removing
one revision cannot invalidate another that has not been reached yet — a nested
revision is dealt with before the one containing it.
"""
revisions = list(iter_revisions(element, parent))
for revision in reversed(revisions):
# -- a revision whose container was already removed is no longer in the tree --
if revision._element.getparent() is None: # pyright: ignore[reportPrivateUsage]
continue
if accept:
revision.accept()
else:
revision.reject()
return len(revisions)
```
### collect_authors
```
collect_authors(revisions: List[Revision]) -> List[str]
```
The distinct authors of `revisions`, in the order they first appear.
Source code in `src/docx/revisions.py`
```
def collect_authors(revisions: List[Revision]) -> List[str]:
"""The distinct authors of `revisions`, in the order they first appear."""
authors: List[str] = []
for revision in revisions:
if revision.author not in authors:
authors.append(revision.author)
return authors
```
## sdt
The ContentControl proxy object for a structured document tag (`w:sdt`).
### ContentControl
```
ContentControl(sdt: CT_Sdt, parent: ProvidesStoryPart)
```
Bases: `Parented`
Proxy for a `w:sdt` element, a structured document tag or "content control".
Content controls are what Word uses for form fields in modern documents, for template placeholders, and for regions bound to a data source. Their content appears in `.paragraphs`, `.iter_inner_content()` and the rest of the read API as though the wrapper were not there; this object is how the wrapper itself is inspected.
Source code in `src/docx/sdt.py`
```
def __init__(self, sdt: CT_Sdt, parent: t.ProvidesStoryPart):
super(ContentControl, self).__init__(parent)
self._element = self._sdt = sdt
```
#### alias
```
alias: str | None
```
The friendly name Word shows on this control, or `None` if not set.
#### id
```
id: int | None
```
The numeric id of this control, or `None` if not set.
#### is_block_level
```
is_block_level: bool
```
`True` when this control wraps block-level content.
A block-level control contains paragraphs or tables; a run-level control sits inside a paragraph and contains runs.
#### paragraphs
```
paragraphs: List[Paragraph]
```
The paragraphs directly inside this control, in document order.
#### runs
```
runs: List[Run]
```
The runs directly inside this control, for a run-level control.
Empty for a block-level control; the runs of such a control are reached through its paragraphs.
#### showing_placeholder
```
showing_placeholder: bool
```
`True` when this control is currently displaying its placeholder text.
The text of such a control is the prompt shown to the user, not a value they entered, which is worth distinguishing when harvesting values from a form.
#### tables
```
tables: List[Table]
```
The tables directly inside this control, in document order.
#### tag
```
tag: str | None
```
The programmatic identifier of this control, or `None` if not set.
Unlike `.alias`, the tag is not shown to the user; it is what code binding to a template matches on.
#### text
```
text: str
```
All the text inside this control.
Paragraphs are separated by newlines, as for a table cell.
#### type
```
type: WD_CONTENT_CONTROL_TYPE | None
```
Member of WdContentControlType, or `None` when the control names no kind.
Word treats a control with no declared kind as rich text, but that is a default rather than a statement, so it is reported as `None` here.
#### iter_inner_content
```
iter_inner_content() -> Iterator[Paragraph | Table]
```
Generate each Paragraph or Table in this control, in document order.
Yields nothing for a run-level control; use `.runs` for one.
Source code in `src/docx/sdt.py`
```
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each |Paragraph| or |Table| in this control, in document order.
Yields nothing for a run-level control; use `.runs` for one.
"""
from docx.table import Table
from docx.text.paragraph import Paragraph
sdtContent = self._sdt.sdtContent
if sdtContent is None:
return
for element in iter_block_content(sdtContent):
yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self))
```
## section
The Section object and related proxy classes.
### \_PageBorders
```
_PageBorders(sectPr: CT_SectPr)
```
Bases: `_Borders`
The border edges drawn around the pages of a section, `section.page_borders`.
Beyond the four edges, `w:pgBorders` carries three of its own settings — see offset_from, display and z_order.
Source code in `src/docx/section.py`
```
def __init__(self, sectPr: CT_SectPr):
super().__init__(CT_PageBorders.edges)
self._sectPr = sectPr
```
#### display
```
display: str | None
```
Which pages the border is drawn on, or `None` when not set.
One of `"allPages"`, `"firstPage"` or `"notFirstPage"`. Word's default, when the attribute is absent, is all pages.
#### offset_from
```
offset_from: str | None
```
What the border is measured from, `"page"` or `"text"`, or `None`.
A certificate frame is measured from the page edge; a border that should track the text block is measured from the text.
#### z_order
```
z_order: str | None
```
Whether the border is drawn `"front"` of or `"back"` of the page content.
`None` when not set.
### Section
```
Section(sectPr: CT_SectPr, document_part: DocumentPart)
```
Document section, providing access to section and page setup settings.
Also provides access to headers and footers.
Source code in `src/docx/section.py`
```
def __init__(self, sectPr: CT_SectPr, document_part: DocumentPart):
super(Section, self).__init__()
self._sectPr = sectPr
self._document_part = document_part
```
#### bidi
```
bidi: bool | None
```
`True` when the default base direction for this section is right-to-left.
This is the section-level default; a paragraph's own `paragraph_format.bidi` overrides it. `None` means inherited, not `False`.
#### text_direction
```
text_direction: WD_TEXT_DIRECTION | None
```
Default flow direction of the text in this section, or `None` when inherited.
Distinct from bidi: this says which way the lines run and whether the glyphs are rotated, not which direction the text reads in.
#### bottom_margin
```
bottom_margin: Length | None
```
Read/write. Bottom margin for pages in this section, in EMU.
`None` when no bottom margin has been specified. Assigning `None` removes any bottom-margin setting.
#### column_count
```
column_count: int
```
Read/write. The number of text columns in this section.
1 for an ordinary single-column section, which is also what an absent `w:cols` element means. Assigning a count leaves the columns of equal width; use set_column_widths for unequal ones.
#### column_separator
```
column_separator: bool
```
Read/write. `True` if a vertical rule is drawn between the columns.
#### column_spacing
```
column_spacing: Length | None
```
Read/write. The space between columns, in EMU, or `None` if not specified.
For columns of unequal width this is the fallback; each column can carry its own spacing, given through set_column_widths.
#### column_widths
```
column_widths: tuple[Length | None, ...]
```
The width of each column, when the columns are of unequal width.
An empty tuple for equal-width columns, whose width Word derives from the page width, the margins and the column spacing rather than stating.
#### different_first_page_header_footer
```
different_first_page_header_footer: bool
```
True if this section displays a distinct first-page header and footer.
Read/write. The definition of the first-page header and footer are accessed using first_page_header and first_page_footer respectively.
#### even_page_footer
```
even_page_footer: _Footer
```
\_Footer object defining footer content for even pages.
The content of this footer definition is ignored unless the document setting odd_and_even_pages_header_footer is set True.
#### even_page_header
```
even_page_header: _Header
```
\_Header object defining header content for even pages.
The content of this header definition is ignored unless the document setting odd_and_even_pages_header_footer is set True.
#### first_page_footer
```
first_page_footer: _Footer
```
\_Footer object defining footer content for the first page of this section.
The content of this footer definition is ignored unless the property different_first_page_header_footer is set True.
#### first_page_header
```
first_page_header: _Header
```
\_Header object defining header content for the first page of this section.
The content of this header definition is ignored unless the property different_first_page_header_footer is set True.
#### footer_distance
```
footer_distance: Length | None
```
Distance from bottom edge of page to bottom edge of the footer.
Read/write. `None` if no setting is present in the XML.
#### watermarks
```
watermarks: List[Watermark]
```
The watermarks in force for this section, in header order.
Empty when the section has none. Ordinarily one per header type, all saying the same thing; they are separate objects because they are separate shapes in separate headers.
#### gutter
```
gutter: Length | None
```
Length object representing page gutter size in English Metric Units.
Read/write. The page gutter is extra spacing added to the `inner` margin to ensure even margins after page binding. Generally only used in book-bound documents with double-sided and facing pages.
This setting applies to all pages in this section.
#### header_distance
```
header_distance: Length | None
```
Distance from top edge of page to top edge of header.
Read/write. `None` if no setting is present in the XML. Assigning `None` causes default value to be used.
#### left_margin
```
left_margin: Length | None
```
Length object representing the left margin for all pages in this section in English Metric Units.
#### orientation
```
orientation: WD_ORIENTATION
```
WdOrientation member specifying page orientation for this section.
One of `WD_ORIENT.PORTRAIT` or `WD_ORIENT.LANDSCAPE`.
Assigning a different orientation also exchanges page_width and page_height, so the page is actually rotated:
```
section.page_width, section.page_height # -- (8.5in, 11in) --
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height # -- (11in, 8.5in) --
```
Underneath, `w:orient` and the `w:w`/`w:h` page dimensions are independent attributes, and setting only the first leaves a section declared landscape at portrait dimensions — which Word renders as portrait. Set page_width and page_height explicitly afterwards for a page size that is not simply the rotation of the current one. Margins (left_margin and friends) are not moved.
#### page_height
```
page_height: Length | None
```
Total page height used for this section.
This value is inclusive of all edge spacing values such as margins.
Page orientation is taken into account, so for example, its expected value would be `Inches(8.5)` for letter-sized paper when orientation is landscape.
#### page_width
```
page_width: Length | None
```
Total page width used for this section.
This value is like "paper size" and includes all edge spacing values such as margins.
Page orientation is taken into account, so for example, its expected value would be `Inches(11)` for letter-sized paper when orientation is landscape.
#### right_margin
```
right_margin: Length | None
```
Length object representing the right margin for all pages in this section in English Metric Units.
#### start_type
```
start_type: WD_SECTION_START
```
Type of page-break (if any) inserted at the start of this section.
For exmple, `WD_SECTION_START.ODD_PAGE` if the section should begin on the next odd page, possibly inserting two page-breaks instead of one.
#### top_margin
```
top_margin: Length | None
```
Length object representing the top margin for all pages in this section in English Metric Units.
#### page_borders
```
page_borders() -> _PageBorders
```
The border edges drawn around the pages of this section:
```
section.page_borders["top"].line = WD_LINE_STYLE.DOUBLE
section.page_borders.offset_from = "page"
```
This is how a certificate or a title page gets its frame. Only the four sides are admitted, unlike a paragraph's borders.
Source code in `src/docx/section.py`
```
@lazyproperty
def page_borders(self) -> _PageBorders:
"""The border edges drawn around the pages of this section::
section.page_borders["top"].line = WD_LINE_STYLE.DOUBLE
section.page_borders.offset_from = "page"
This is how a certificate or a title page gets its frame. Only the four sides
are admitted, unlike a paragraph's borders.
"""
return _PageBorders(self._sectPr)
```
#### footer
```
footer() -> _Footer
```
\_Footer object representing default page footer for this section.
The default footer is used for odd-numbered pages when separate odd/even footers are enabled. It is used for both odd and even-numbered pages otherwise.
Source code in `src/docx/section.py`
```
@lazyproperty
def footer(self) -> _Footer:
"""|_Footer| object representing default page footer for this section.
The default footer is used for odd-numbered pages when separate odd/even footers
are enabled. It is used for both odd and even-numbered pages otherwise.
"""
return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY)
```
#### add_text_watermark
```
add_text_watermark(
text: str,
*,
font: str = "Calibri",
font_size: Length | int | None = None,
color: str = "C0C0C0",
opacity: float | None = None,
angle: float = 315,
width: Length | int = Pt(468),
height: Length | int = Pt(234),
bold: bool = False,
italic: bool = False,
) -> List[Watermark]
```
Add a text watermark to this section, returning the watermarks added.
The watermark goes into all three header types — default, first-page and even-page — so it does not vanish on a page that uses a different header:
```
section.add_text_watermark("DRAFT")
section.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0)
```
`color` is an RGB hex string; Word's own "Semitransparent" watermark is simply a light grey, which is the default here. `opacity` additionally sets true VML transparency, between 0 and 1. `angle` is the rotation in degrees, 315 giving Word's diagonal watermark and 0 a horizontal one.
The text is stretched to fill a box of `width` by `height`, which is how Word sizes a watermark; the defaults are Word's own. Passing `font_size` renders the text at that size instead of stretching it.
Where this section's headers are inherited from an earlier section, the watermark is written into the header actually in force, which that earlier section shares. An inherited header is the same header, so there is no way to mark up one section's copy of it alone without first setting `is_linked_to_previous = False`.
Source code in `src/docx/section.py`
```
def add_text_watermark(
self,
text: str,
*,
font: str = "Calibri",
font_size: Length | int | None = None,
color: str = "C0C0C0",
opacity: float | None = None,
angle: float = 315,
width: Length | int = Pt(468),
height: Length | int = Pt(234),
bold: bool = False,
italic: bool = False,
) -> List[Watermark]:
"""Add a text watermark to this section, returning the watermarks added.
The watermark goes into all three header types — default, first-page and
even-page — so it does not vanish on a page that uses a different header::
section.add_text_watermark("DRAFT")
section.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0)
`color` is an RGB hex string; Word's own "Semitransparent" watermark is simply a
light grey, which is the default here. `opacity` additionally sets true VML
transparency, between 0 and 1. `angle` is the rotation in degrees, 315 giving
Word's diagonal watermark and 0 a horizontal one.
The text is stretched to fill a box of `width` by `height`, which is how Word
sizes a watermark; the defaults are Word's own. Passing `font_size` renders the
text at that size instead of stretching it.
Where this section's headers are inherited from an earlier section, the
watermark is written into the header actually in force, which that earlier
section shares. An inherited header is the same header, so there is no way to
mark up one section's copy of it alone without first setting
`is_linked_to_previous = False`.
"""
from docx.watermark import add_text_watermark, iter_watermark_headers
return add_text_watermark(
iter_watermark_headers([self]),
text,
font=font,
font_size=font_size,
color=color,
opacity=opacity,
angle=angle,
width=width,
height=height,
bold=bold,
italic=italic,
)
```
#### add_image_watermark
```
add_image_watermark(
image_path_or_stream: 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 this section, returning the watermarks added.
As with add_text_watermark, all three header types get the watermark.
`width` and `height` scale the image the same way Run.add_picture does, defaulting to its native size; `scale` multiplies whatever that works out to. `washout` applies Word's brightness-and-contrast correction, which is what turns a logo into a pale background image rather than an opaque one over the text.
Source code in `src/docx/section.py`
```
def add_image_watermark(
self,
image_path_or_stream: 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 this section, returning the watermarks added.
As with :meth:`add_text_watermark`, all three header types get the watermark.
`width` and `height` scale the image the same way :meth:`.Run.add_picture` does,
defaulting to its native size; `scale` multiplies whatever that works out to.
`washout` applies Word's brightness-and-contrast correction, which is what turns
a logo into a pale background image rather than an opaque one over the text.
"""
from docx.watermark import add_image_watermark, iter_watermark_headers
return add_image_watermark(
iter_watermark_headers([self]),
image_path_or_stream,
width=width,
height=height,
washout=washout,
scale=scale,
)
```
#### remove_watermark
```
remove_watermark() -> int
```
Remove every watermark from this section, returning how many were removed.
Source code in `src/docx/section.py`
```
def remove_watermark(self) -> int:
"""Remove every watermark from this section, returning how many were removed."""
from docx.watermark import iter_watermark_headers, remove_watermarks
return remove_watermarks(iter_watermark_headers([self]))
```
#### iter_headers_footers
```
iter_headers_footers() -> Iterator[_Header | _Footer]
```
Generate all six header and footer objects of this section.
The default, first-page and even-page header come first, then the three footers. All six are generated whether or not they are in use: whether a first-page header is shown depends on different_first_page_header_footer, and whether it is defined here or inherited from the prior section is is_linked_to_previous. This is for code that needs to visit each of them, such as a document-wide search.
Source code in `src/docx/section.py`
```
def iter_headers_footers(self) -> Iterator[_Header | _Footer]:
"""Generate all six header and footer objects of this section.
The default, first-page and even-page header come first, then the three footers.
All six are generated whether or not they are in use: whether a first-page
header is shown depends on :attr:`different_first_page_header_footer`, and
whether it is defined here or inherited from the prior section is
:attr:`~._BaseHeaderFooter.is_linked_to_previous`. This is for code that needs
to visit each of them, such as a document-wide search.
"""
yield self.header
yield self.first_page_header
yield self.even_page_header
yield self.footer
yield self.first_page_footer
yield self.even_page_footer
```
#### header
```
header() -> _Header
```
\_Header object representing default page header for this section.
The default header is used for odd-numbered pages when separate odd/even headers are enabled. It is used for both odd and even-numbered pages otherwise.
Source code in `src/docx/section.py`
```
@lazyproperty
def header(self) -> _Header:
"""|_Header| object representing default page header for this section.
The default header is used for odd-numbered pages when separate odd/even headers
are enabled. It is used for both odd and even-numbered pages otherwise.
"""
return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY)
```
#### iter_inner_content
```
iter_inner_content() -> Iterator[Paragraph | Table]
```
Generate each Paragraph or Table object in this `section`.
Items appear in document order.
Source code in `src/docx/section.py`
```
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each Paragraph or Table object in this `section`.
Items appear in document order.
"""
for element in self._sectPr.iter_inner_content():
yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self))
```
#### set_column_widths
```
set_column_widths(
widths: Sequence[Length],
spacings: Sequence[Length] | None = None,
) -> None
```
Lay this section out in columns of the given `widths`.
`spacings` gives the space following each column and defaults to the section's `.column_spacing` for every column. It must be the same length as `widths` when given; the value for the last column is written but has no visible effect.
The equal-width case is the common one and is better expressed by assigning `.column_count`, which this replaces. Pass a single width to go back to one column.
Source code in `src/docx/section.py`
```
def set_column_widths(
self, widths: Sequence[Length], spacings: Sequence[Length] | None = None
) -> None:
"""Lay this section out in columns of the given `widths`.
`spacings` gives the space following each column and defaults to the section's
`.column_spacing` for every column. It must be the same length as `widths` when
given; the value for the last column is written but has no visible effect.
The equal-width case is the common one and is better expressed by assigning
`.column_count`, which this replaces. Pass a single width to go back to one
column.
"""
if not widths:
raise ValueError("at least one column width is required")
if spacings is not None and len(spacings) != len(widths):
raise ValueError(
"spacings must have one value per column, got %d for %d columns"
% (len(spacings), len(widths))
)
cols = self._sectPr.get_or_add_cols()
cols.clear_cols()
cols.num = len(widths)
cols.equalWidth = False
for idx, width in enumerate(widths):
col = cols.add_col()
col.w = width
if spacings is not None:
col.space = spacings[idx]
```
### Sections
```
Sections(
document_elm: CT_Document, document_part: DocumentPart
)
```
Bases: `Sequence[Section]`
Sequence of Section objects corresponding to the sections in the document.
Supports `len()`, iteration, and indexed access.
Source code in `src/docx/section.py`
```
def __init__(self, document_elm: CT_Document, document_part: DocumentPart):
super(Sections, self).__init__()
self._document_elm = document_elm
self._document_part = document_part
```
### \_BaseHeaderFooter
```
_BaseHeaderFooter(
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
)
```
Bases: `BlockItemContainer`
Base class for header and footer classes.
Source code in `src/docx/section.py`
```
def __init__(
self,
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
):
self._sectPr = sectPr
self._document_part = document_part
self._hdrftr_index = header_footer_index
```
#### is_linked_to_previous
```
is_linked_to_previous: bool
```
`True` if this header/footer uses the definition from the prior section.
`False` if this header/footer has an explicit definition.
Assigning `True` to this property removes the header/footer definition for this section, causing it to "inherit" the corresponding definition of the prior section. Assigning `False` causes a new, empty definition to be added for this section, but only if no definition is already present.
#### part
```
part: HeaderPart | FooterPart
```
The HeaderPart or FooterPart for this header/footer.
This overrides `BlockItemContainer.part` and is required to support image insertion and perhaps other content like hyperlinks.
### \_Footer
```
_Footer(
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
)
```
Bases: `_BaseHeaderFooter`
Page footer, used for all three types (default, even-page, and first-page).
Note that, like a document or table cell, a footer must contain a minimum of one paragraph and a new or otherwise "empty" footer contains a single empty paragraph. This first paragraph can be accessed as `footer.paragraphs[0]` for purposes of adding content to it. Using `add_paragraph()` by itself to add content will leave an empty paragraph above the newly added one.
Source code in `src/docx/section.py`
```
def __init__(
self,
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
):
self._sectPr = sectPr
self._document_part = document_part
self._hdrftr_index = header_footer_index
```
### \_Header
```
_Header(
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
)
```
Bases: `_BaseHeaderFooter`
Page header, used for all three types (default, even-page, and first-page).
Note that, like a document or table cell, a header must contain a minimum of one paragraph and a new or otherwise "empty" header contains a single empty paragraph. This first paragraph can be accessed as `header.paragraphs[0]` for purposes of adding content to it. Using `add_paragraph()` by itself to add content will leave an empty paragraph above the newly added one.
Source code in `src/docx/section.py`
```
def __init__(
self,
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
):
self._sectPr = sectPr
self._document_part = document_part
self._hdrftr_index = header_footer_index
```
## settings
Settings object, providing access to document-level settings.
### Settings
```
Settings(
element: BaseOxmlElement,
parent: ProvidesXmlPart | None = None,
)
```
Bases: `ElementProxy`
Provides access to document-level settings for a document.
Accessed using the Document.settings property.
Source code in `src/docx/settings.py`
```
def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None):
super().__init__(element, parent)
self._settings = cast("CT_Settings", element)
```
#### odd_and_even_pages_header_footer
```
odd_and_even_pages_header_footer: bool
```
True if this document has distinct odd and even page headers and footers.
Read/write.
#### track_revisions
```
track_revisions: bool
```
True when Word records changes made to this document as tracked changes.
Read/write. This is the "Track Changes" toggle. Setting it does not mark anything already in the document as a revision; it asks Word to record what happens from now on.
#### update_fields_on_open
```
update_fields_on_open: bool
```
True when Word should recalculate every field when it opens this document.
Read/write. This library cannot compute a field result — a table of contents added here is empty, and a `PAGE` field has no page number, because both depend on how Word lays the document out. Setting this asks Word to fill them in as soon as the document is opened, which is the only way to get a populated table of contents out of a generated document.
Word prompts the reader before updating when the document has a table of contents, so a document saved with this set may show that prompt once.
## shape
Objects related to shapes.
A shape is a visual object that appears on the drawing layer of a document.
### \_PictureShape
The picture-extraction half of an inline or floating shape.
Both `wp:inline` and `wp:anchor` wrap the same `a:graphic` subtree, so finding the image behind them is one implementation rather than two.
#### image
```
image: Image | None
```
The Image this shape displays, or `None` when there is no image to return.
This is the counterpart of Run.add_picture — extracting the pictures from a document without walking the relationships by hand:
```
for shape in document.inline_shapes:
if shape.image is not None:
Path(f"{shape.image.sha1}.{shape.image.ext}").write_bytes(
shape.image.blob
)
```
`None` in three cases, each of which is a real document rather than an error:
- the shape is not a picture at all — a chart, a SmartArt diagram or an embedded object;
- the picture is *linked* rather than embedded, so the bytes are not in the package and there is nothing to hand back;
- the shape was constructed without a parent, so there is no part to resolve the relationship against.
For an SVG picture this returns the raster fallback, which is what every consumer can decode; svg_image returns the vector source.
Several shapes can share one image part, so the Image returned for two shapes may be the same object.
#### svg_image
```
svg_image: Image | None
```
The SVG source of this picture, or `None` when it has none.
Word records an SVG picture as an `asvg:svgBlip` extension *alongside* a raster rendering of it, rather than in place of one. image returns the raster fallback; this returns the vector original.
### InlineShapes
```
InlineShapes(body_elm: CT_Body, parent: StoryPart)
```
Bases: `Parented`
Sequence of InlineShape instances, supporting len(), iteration, and indexed access.
Source code in `src/docx/shape.py`
```
def __init__(self, body_elm: CT_Body, parent: StoryPart):
super(InlineShapes, self).__init__(parent)
self._body = body_elm
```
### FloatingShapes
```
FloatingShapes(body_elm: CT_Body, parent: StoryPart)
```
Bases: `Parented`
Sequence of FloatingShape instances, supporting len(), iteration and indexing.
A floating shape is anchored rather than inline: it is positioned against the page, the margin, the column or the paragraph, and text wraps around it. These are a distinct collection from InlineShapes rather than part of it, because almost nothing that is true of an inline shape's position is true of a floating one's, and silently mixing the two is how code that walks `inline_shapes` starts reporting nonsense positions.
Source code in `src/docx/shape.py`
```
def __init__(self, body_elm: CT_Body, parent: StoryPart):
super().__init__(parent)
self._body = body_elm
```
### FloatingShape
```
FloatingShape(
anchor: CT_Anchor,
parent: ProvidesStoryPart | None = None,
)
```
Bases: `_PictureShape`
Proxy for a `` element, a shape that text flows around.
Reached through Document.floating_shapes or returned by Run.add_float_picture.
Source code in `src/docx/shape.py`
```
def __init__(self, anchor: CT_Anchor, parent: t.ProvidesStoryPart | None = None):
self._anchor = anchor
self._parent = parent
```
#### allow_overlap
```
allow_overlap: bool
```
Whether this shape may overlap another floating shape. Read/write.
#### behind_text
```
behind_text: bool
```
Whether this shape is drawn behind the document text rather than over it.
Read/write. This is what "put the watermark behind the text" means; it takes effect only with wrap_type of `WD_WRAP_TYPE.NONE`, since any other wrap setting keeps text out of the shape's way in the first place.
#### description
```
description: str | None
```
The alternative text of this shape, `None` if not set. Read/write.
#### height
```
height: Length
```
The display height of this shape as an Emu instance. Read/write.
#### horizontal_align
```
horizontal_align: WD_ANCHOR_ALIGN_H | None
```
Named horizontal alignment of this shape, `None` when an offset is used.
Read/write. Assigning an alignment replaces any left offset, and vice versa: the schema allows only one of the two, and Word ignores a shape that has both. Assigning `None` leaves the shape with neither, which Word treats as an offset of zero.
#### left
```
left: Length | None
```
Horizontal offset from relative_from_h, `None` when aligned instead.
Read/write. See horizontal_align for how the two interact.
#### relative_from_h
```
relative_from_h: WD_ANCHOR_RELATIVE_FROM_H
```
What left and horizontal_align are measured from. Read/write.
#### relative_from_v
```
relative_from_v: WD_ANCHOR_RELATIVE_FROM_V
```
What top and vertical_align are measured from. Read/write.
#### title
```
title: str | None
```
The title of this shape, `None` if not set. Read/write.
#### top
```
top: Length | None
```
Vertical offset from relative_from_v, `None` when aligned instead.
Read/write.
#### vertical_align
```
vertical_align: WD_ANCHOR_ALIGN_V | None
```
Named vertical alignment of this shape, `None` when an offset is used.
Read/write. See horizontal_align.
#### width
```
width: Length
```
The display width of this shape as an Emu instance. Read/write.
#### wrap_distance
```
wrap_distance: tuple[Length, Length, Length, Length]
```
Space held clear of this shape as `(top, right, bottom, left)`. Read-only.
Set the individual distances with set_wrap_distance.
#### wrap_type
```
wrap_type: WD_WRAP_TYPE
```
Member of WdWrapType describing how text wraps around this shape.
Read/write.
#### z_order
```
z_order: int
```
Position of this shape in the stack of floating shapes. Read/write.
A higher value is drawn on top of a lower one. Independent of behind_text, which decides whether the whole floating layer this shape is in sits in front of the text or behind it.
#### set_wrap_distance
```
set_wrap_distance(
top: Length | int | None = None,
right: Length | int | None = None,
bottom: Length | int | None = None,
left: Length | int | None = None,
) -> None
```
Set the space held clear of this shape when text wraps around it.
Each argument left as `None` is unchanged. Word's own default is no clearance above and below and 0.13cm to each side, which is what a new floating picture gets here.
Source code in `src/docx/shape.py`
```
def set_wrap_distance(
self,
top: Length | int | None = None,
right: Length | int | None = None,
bottom: Length | int | None = None,
left: Length | int | None = None,
) -> None:
"""Set the space held clear of this shape when text wraps around it.
Each argument left as |None| is unchanged. Word's own default is no clearance
above and below and 0.13cm to each side, which is what a new floating picture
gets here.
"""
anchor = self._anchor
if top is not None:
anchor.distT = int(top)
if right is not None:
anchor.distR = int(right)
if bottom is not None:
anchor.distB = int(bottom)
if left is not None:
anchor.distL = int(left)
```
### InlineShape
```
InlineShape(
inline: CT_Inline,
parent: ProvidesStoryPart | None = None,
)
```
Bases: `_PictureShape`
Proxy for an `` element, representing the container for an inline graphical object.
Source code in `src/docx/shape.py`
```
def __init__(self, inline: CT_Inline, parent: t.ProvidesStoryPart | None = None):
super(InlineShape, self).__init__()
self._inline = inline
self._parent = parent
```
#### description
```
description: str | None
```
Read/write.
The alternative text of this shape, `None` if not set.
This is what a screen reader announces in place of the picture, and what an automated accessibility check looks for. Word's "Alt Text" pane writes this field. Assigning `None` removes it.
#### height
```
height: Length
```
Read/write.
The display height of this inline shape as an Emu instance.
#### type
```
type
```
The type of this inline shape as a member of `docx.enum.shape.WD_INLINE_SHAPE`, e.g. `LINKED_PICTURE`.
Read-only.
#### title
```
title: str | None
```
Read/write.
The title of this shape, `None` if not set.
Word presents this separately from the alternative text and screen readers do not generally announce it; `.description` is the one accessibility depends on. Assigning `None` removes it.
#### width
```
width
```
Read/write.
The display width of this inline shape as an Emu instance.
## shared
Objects shared by docx modules.
### Length
Bases: `int`
Base class for length constructor classes Inches, Cm, Mm, Px, and Emu.
Behaves as an int count of English Metric Units, 914,400 to the inch, 36,000 to the mm. Provides convenience unit conversion methods in the form of read-only properties. Immutable.
#### cm
```
cm
```
The equivalent length expressed in centimeters (float).
#### emu
```
emu
```
The equivalent length expressed in English Metric Units (int).
#### inches
```
inches
```
The equivalent length expressed in inches (float).
#### mm
```
mm
```
The equivalent length expressed in millimeters (float).
#### pt
```
pt
```
Floating point length in points.
#### twips
```
twips
```
The equivalent length expressed in twips (int).
### Inches
Bases: `Length`
Convenience constructor for length in inches, e.g. `width = Inches(0.5)`.
### Cm
Bases: `Length`
Convenience constructor for length in centimeters, e.g. `height = Cm(12)`.
### Emu
Bases: `Length`
Convenience constructor for length in English Metric Units, e.g. `width = Emu(457200)`.
### Mm
Bases: `Length`
Convenience constructor for length in millimeters, e.g. `width = Mm(240.5)`.
### Pt
Bases: `Length`
Convenience value class for specifying a length in points.
### Twips
Bases: `Length`
Convenience constructor for length in twips, e.g. `width = Twips(42)`.
A twip is a twentieth of a point, 635 EMU.
### Pct
Bases: `float`
A percentage, e.g. `Pct(100)` is one hundred percent.
A percentage is deliberately *not* a Length. Every unit on Length is absolute and reducible to EMU; a percentage is relative to something else and has no size of its own, so the two do not belong to the same family and mixing them silently produces nonsense.
Word stores these in fiftieths of a percent, which is what `.fiftieths` returns and what `from_fiftieths()` reads.
#### fiftieths
```
fiftieths: int
```
This percentage in fiftieths of a percent, e.g. `Pct(100).fiftieths` is 5000.
#### from_fiftieths
```
from_fiftieths(value: int) -> Pct
```
A Pct from `value` fiftieths of a percent, the form Word writes.
Source code in `src/docx/shared.py`
```
@classmethod
def from_fiftieths(cls, value: int) -> Pct:
"""A |Pct| from `value` fiftieths of a percent, the form Word writes."""
return cls(value / 50.0)
```
### RGBColor
Bases: `Tuple[int, int, int]`
Immutable value object defining a particular RGB color.
#### from_string
```
from_string(rgb_hex_str: str) -> RGBColor
```
Return a new instance from an RGB color hex string like `'3C2F80'`.
A leading "#" is accepted, so CSS-style values like `'#3C2F80'` also work.
Source code in `src/docx/shared.py`
```
@classmethod
def from_string(cls, rgb_hex_str: str) -> RGBColor:
"""Return a new instance from an RGB color hex string like ``'3C2F80'``.
A leading "#" is accepted, so CSS-style values like ``'#3C2F80'`` also work.
"""
rgb_hex_str = rgb_hex_str.lstrip("#")
r = int(rgb_hex_str[:2], 16)
g = int(rgb_hex_str[2:4], 16)
b = int(rgb_hex_str[4:], 16)
return cls(r, g, b)
```
### lazyproperty
```
lazyproperty(fget: Callable[..., T])
```
Bases: `Generic[T]`
Decorator like @property, but evaluated only on first access.
Like @property, this can only be used to decorate methods having only a `self` parameter, and is accessed like an attribute on an instance, i.e. trailing parentheses are not used. Unlike @property, the decorated method is only evaluated on first access; the resulting value is cached and that same value returned on second and later access without re-evaluation of the method.
Like @property, this class produces a *data descriptor* object, which is stored in the **dict** of the *class* under the name of the decorated method ('fget' nominally). The cached value is stored in the **dict** of the *instance* under that same name.
Because it is a data descriptor (as opposed to a *non-data descriptor*), its `__get__()` method is executed on each access of the decorated attribute; the **dict** item of the same name is "shadowed" by the descriptor.
While this may represent a performance improvement over a property, its greater benefit may be its other characteristics. One common use is to construct collaborator objects, removing that "real work" from the constructor, while still only executing once. It also de-couples client code from any sequencing considerations; if it's accessed from more than one location, it's assured it will be ready whenever needed.
Loosely based on: https://stackoverflow.com/a/6849299/1902513.
A lazyproperty is read-only. There is no counterpart to the optional "setter" (or deleter) behavior of an @property. This is critically important to maintaining its immutability and idempotence guarantees. Attempting to assign to a lazyproperty raises AttributeError unconditionally.
The parameter names in the methods below correspond to this usage example:
```
class Obj(object)
@lazyproperty
def fget(self):
return 'some result'
obj = Obj()
```
Not suitable for wrapping a function (as opposed to a method) because it is not callable.
*fget* is the decorated method (a "getter" function).
A lazyproperty is read-only, so there is only an *fget* function (a regular @property can also have an fset and fdel function). This name was chosen for consistency with Python's `property` class which uses this name for the corresponding parameter.
Source code in `src/docx/shared.py`
```
def __init__(self, fget: Callable[..., T]) -> None:
"""*fget* is the decorated method (a "getter" function).
A lazyproperty is read-only, so there is only an *fget* function (a regular
@property can also have an fset and fdel function). This name was chosen for
consistency with Python's `property` class which uses this name for the
corresponding parameter.
"""
# --- maintain a reference to the wrapped getter method
self._fget = fget
# --- and store the name of that decorated method
self._name = fget.__name__
# --- adopt fget's __name__, __doc__, and other attributes
functools.update_wrapper(self, fget) # pyright: ignore
```
### ElementProxy
```
ElementProxy(
element: BaseOxmlElement,
parent: ProvidesXmlPart | None = None,
)
```
Base class for lxml element proxy classes.
An element proxy class is one whose primary responsibilities are fulfilled by manipulating the attributes and child elements of an XML element. They are the most common type of class in python-docx other than custom element (oxml) classes.
Source code in `src/docx/shared.py`
```
def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None):
self._element = element
self._parent = parent
```
#### element
```
element
```
The lxml element proxied by this object.
#### part
```
part: XmlPart
```
The package part containing this object.
### Parented
```
Parented(parent: ProvidesXmlPart)
```
Provides common services for document elements that occur below a part but may occasionally require an ancestor object to provide a service, such as add or drop a relationship.
Provides `self._parent` attribute to subclasses.
Source code in `src/docx/shared.py`
```
def __init__(self, parent: t.ProvidesXmlPart):
self._parent = parent
```
#### part
```
part: XmlPart
```
The package part containing this object.
### StoryChild
```
StoryChild(parent: ProvidesStoryPart)
```
A document element within a story part.
Story parts include DocumentPart and Header/FooterPart and can contain block items (paragraphs and tables). Items from the block-item subtree occasionally require an ancestor object to provide access to part-level or package-level items like styles or images or to add or drop a relationship.
Provides `self._parent` attribute to subclasses.
Source code in `src/docx/shared.py`
```
def __init__(self, parent: t.ProvidesStoryPart):
self._parent = parent
```
#### part
```
part: StoryPart
```
The package part containing this object.
### TextAccumulator
```
TextAccumulator(separator: str = '')
```
Accepts `str` fragments and joins them together, in order, on \`.pop().
Handy when text in a stream is broken up arbitrarily and you want to join it back together within certain bounds. The optional `separator` argument determines how the text fragments are punctuated, defaulting to the empty string.
Source code in `src/docx/shared.py`
```
def __init__(self, separator: str = ""):
self._separator = separator
self._texts: List[str] = []
```
#### push
```
push(text: str) -> None
```
Add a text fragment to the accumulator.
Source code in `src/docx/shared.py`
```
def push(self, text: str) -> None:
"""Add a text fragment to the accumulator."""
self._texts.append(text)
```
#### pop
```
pop() -> Iterator[str]
```
Generate sero-or-one str from those accumulated.
Using `yield from accum.pop()` in a generator setting avoids producing an empty string when no text is in the accumulator.
Source code in `src/docx/shared.py`
```
def pop(self) -> Iterator[str]:
"""Generate sero-or-one str from those accumulated.
Using `yield from accum.pop()` in a generator setting avoids producing an empty
string when no text is in the accumulator.
"""
if not self._texts:
return
text = self._separator.join(self._texts)
self._texts.clear()
yield text
```
### write_only_property
```
write_only_property(f: Callable[[Any, Any], None])
```
@write_only_property decorator.
Creates a property (descriptor attribute) that accepts assignment, but not getattr (use in an expression).
Source code in `src/docx/shared.py`
```
def write_only_property(f: Callable[[Any, Any], None]):
"""@write_only_property decorator.
Creates a property (descriptor attribute) that accepts assignment, but not getattr
(use in an expression).
"""
docstring = f.__doc__
return property(fset=f, doc=docstring)
```
## table
The Table object and related proxy classes.
### \_TableBorders
```
_TableBorders(tbl: CT_Tbl)
```
Bases: `_Borders`
The border edges of a table, `table.borders`.
Source code in `src/docx/table.py`
```
def __init__(self, tbl: CT_Tbl):
# -- the edges come from the element class rather than being repeated here, so
# -- the mapping cannot drift from the schema sequence the element declares --
super().__init__(CT_TblBorders.edges)
self._tbl = tbl
```
### \_CellBorders
```
_CellBorders(tc: CT_Tc)
```
Bases: `_Borders`
The border edges of a table cell, `cell.borders`.
Source code in `src/docx/table.py`
```
def __init__(self, tc: CT_Tc):
super().__init__(CT_TcBorders.edges)
self._tc = tc
```
### \_TableLook
```
_TableLook(tbl: CT_Tbl)
```
Which parts of the table style apply to a table, `table.look`.
`w:tblLook` is what tells Word whether the first row is a header row, whether the first or last column is emphasised, and whether row or column banding is on. Without it a styled table looks nothing like the style preview in Word.
Each flag is tri-state: `None` means the attribute is absent and Word applies its own default (off for every flag). The two banding flags are stored inverted in the XML, as `w:noHBand` and `w:noVBand`; that inversion lives here so the oxml layer stays faithful to the attribute names.
Word writes the six named attributes *and* the equivalent legacy bitmask in `@w:val`, and keeps them in step. Setting any flag through this proxy rewrites `@w:val` to match, because some older consumers read only the bitmask.
Source code in `src/docx/table.py`
```
def __init__(self, tbl: CT_Tbl):
self._tbl = tbl
```
#### first_row
```
first_row: bool | None
```
`True` when the table style's first-row (header) formatting applies.
#### last_row
```
last_row: bool | None
```
`True` when the table style's last-row (total) formatting applies.
#### first_column
```
first_column: bool | None
```
`True` when the table style's first-column formatting applies.
#### last_column
```
last_column: bool | None
```
`True` when the table style's last-column formatting applies.
#### horizontal_banding
```
horizontal_banding: bool | None
```
`True` when the table style's row banding applies.
Stored inverted, as `w:noHBand`.
#### vertical_banding
```
vertical_banding: bool | None
```
`True` when the table style's column banding applies.
Stored inverted, as `w:noVBand`.
### \_TableCellMargins
```
_TableCellMargins(tbl: CT_Tbl)
```
The default cell margins of a table, `table.cell_margins`.
These are the padding Word applies inside every cell of the table that does not override them. An edge reads `None` when the table sets no value for it, in which case the table style's value applies.
The `start` and `end` edges are the logical (writing-direction) synonyms of `left` and `right`. Word writes `left` and `right`; both are exposed because documents from other producers use the newer pair.
Source code in `src/docx/table.py`
```
def __init__(self, tbl: CT_Tbl):
self._tbl = tbl
```
#### clear
```
clear() -> None
```
Remove the `w:tblCellMar` element, restoring the table style's margins.
Source code in `src/docx/table.py`
```
def clear(self) -> None:
"""Remove the `w:tblCellMar` element, restoring the table style's margins."""
self._tbl.tblPr._remove_tblCellMar() # pyright: ignore[reportPrivateUsage]
```
### Table
```
Table(tbl: CT_Tbl, parent: ProvidesStoryPart)
```
Bases: `StoryChild`
Proxy class for a WordprocessingML `` element.
Source code in `src/docx/table.py`
```
def __init__(self, tbl: CT_Tbl, parent: t.ProvidesStoryPart):
super(Table, self).__init__(parent)
self._element = tbl
self._tbl = tbl
```
#### alignment
```
alignment: WD_TABLE_ALIGNMENT | None
```
Read/write.
A member of WdRowAlignment or None, specifying the positioning of this table between the page margins. `None` if no setting is specified, causing the effective value to be inherited from the style hierarchy.
#### autofit
```
autofit: bool
```
`True` if column widths can be automatically adjusted to improve the fit of cell contents.
`False` if table layout is fixed. Column widths are adjusted in either case if total column width exceeds page width. Read/write boolean.
#### description
```
description: str | None
```
Alternative-text description for this table, or `None` if not set.
Assigning `None` removes the description. This value is stored in the `w:tblDescription` table-property element and is used by assistive technologies.
#### indent
```
indent: Length | None
```
Indentation of this table from the margin, or `None` if not set.
This is `w:tblInd`. Assigning `None` removes it.
#### width
```
width: Length | Pct | None
```
The preferred width of this table.
A Length for an absolute width, a Pct for a percentage of the text column, and `None` when the width is `auto` — Word sizing the table to its content — or no `w:tblW` is present at all.
A percentage table reflows with the page margins where one built from absolute column widths does not, so `table.width = Pct(100)` is not the same as setting the column widths to add up:
```
table.width = Pct(100)
table.width = Inches(6)
table.width = None # auto
```
Note this is the *preferred* width: Word may widen a table whose content does not fit, and a table with `autofit` on will do so routinely.
#### style
```
style: _TableStyle | None
```
\_TableStyle object representing the style applied to this table.
Read/write. The default table style for the document (often `Normal Table`) is returned if the table has no directly-applied style. Assigning `None` to this property removes any directly-applied table style causing it to inherit the default table style of the document.
Note that the style name of a table style differs slightly from that displayed in the user interface; a hyphen, if it appears, must be removed. For example, `Light Shading - Accent 1` becomes `Light Shading Accent 1`.
#### table
```
table
```
Provide child objects with reference to the Table object they belong to, without them having to know their direct parent is a Table object.
This is the terminus of a series of `parent._table` calls from an arbitrary child through its ancestors.
#### table_direction
```
table_direction: WD_TABLE_DIRECTION | None
```
Member of WdTableDirection indicating cell-ordering direction.
For example: `WD_TABLE_DIRECTION.LTR`. `None` indicates the value is inherited from the style hierarchy.
#### title
```
title: str | None
```
Alternative-text title for this table, or `None` if not set.
Assigning `None` removes the title. This value is stored in the `w:tblCaption` table-property element and is used by assistive technologies.
#### add_column
```
add_column(width: Length)
```
Return a \_Column object of `width`, newly added rightmost to the table.
Source code in `src/docx/table.py`
```
def add_column(self, width: Length):
"""Return a |_Column| object of `width`, newly added rightmost to the table."""
tblGrid = self._tbl.tblGrid
gridCol = tblGrid.add_gridCol()
gridCol.w = width
for tr in self._tbl.tr_lst:
tc = tr.add_tc()
tc.width = width
return _Column(gridCol, self)
```
#### add_row
```
add_row()
```
Return a \_Row instance, newly added bottom-most to the table.
Source code in `src/docx/table.py`
```
def add_row(self):
"""Return a |_Row| instance, newly added bottom-most to the table."""
tbl = self._tbl
tr = tbl.add_tr()
for gridCol in tbl.tblGrid.gridCol_lst:
tc = tr.add_tc()
if gridCol.w is not None:
tc.width = gridCol.w
return _Row(tr, self)
```
#### borders
```
borders() -> _TableBorders
```
The border edges of this table, as a mapping keyed by edge name:
```
table.borders["top"].line = WD_LINE_STYLE.SINGLE
table.borders["top"].size = Pt(1)
```
These are the borders applied to the table as a whole; `insideH` and `insideV` set the horizontal and vertical borders between its cells. A border set on an individual cell through `cell.borders` takes precedence over the table border at that edge.
Source code in `src/docx/table.py`
```
@lazyproperty
def borders(self) -> _TableBorders:
"""The border edges of this table, as a mapping keyed by edge name::
table.borders["top"].line = WD_LINE_STYLE.SINGLE
table.borders["top"].size = Pt(1)
These are the borders applied to the table as a whole; `insideH` and `insideV`
set the horizontal and vertical borders between its cells. A border set on an
individual cell through `cell.borders` takes precedence over the table border
at that edge.
"""
return _TableBorders(self._tbl)
```
#### cell
```
cell(row_idx: int, col_idx: int) -> _Cell
```
\_Cell at `row_idx`, `col_idx` intersection.
(0, 0) is the top, left-most cell. Negative indices count back from the end, as for a sequence.
Raises `IndexError` if `row_idx` is out of range, or if the row does not occupy layout-grid column `col_idx` — Word allows a row to start late or end early.
The target cell is located directly, without materializing the whole layout grid, so reading a table cell-by-cell costs time proportional to the number of cells rather than to its square.
Source code in `src/docx/table.py`
```
def cell(self, row_idx: int, col_idx: int) -> _Cell:
"""|_Cell| at `row_idx`, `col_idx` intersection.
(0, 0) is the top, left-most cell. Negative indices count back from the end, as
for a sequence.
Raises |IndexError| if `row_idx` is out of range, or if the row does not occupy
layout-grid column `col_idx` — Word allows a row to start late or end early.
The target cell is located directly, without materializing the whole layout
grid, so reading a table cell-by-cell costs time proportional to the number of
cells rather than to its square.
"""
tr = self._tbl.tr_at_idx(row_idx)
if col_idx < 0:
col_idx += self._column_count
try:
tc = tr.tc_covering_grid_offset(col_idx)
except ValueError:
raise IndexError("table column index [%d] is out of range" % col_idx) from None
# -- a continuation cell of a vertical span holds no content; the cell the span
# -- starts at does --
return _Cell(tc.top_tc, self)
```
#### column_cells
```
column_cells(column_idx: int) -> list[_Cell]
```
Sequence of cells in the column at `column_idx` in this table.
A row that does not occupy `column_idx`, because it starts late or ends early, contributes no cell.
Source code in `src/docx/table.py`
```
def column_cells(self, column_idx: int) -> list[_Cell]:
"""Sequence of cells in the column at `column_idx` in this table.
A row that does not occupy `column_idx`, because it starts late or ends early,
contributes no cell.
"""
def iter_column_cells() -> Iterator[_Cell]:
for tr in self._tbl.tr_lst:
try:
tc = tr.tc_covering_grid_offset(column_idx)
except ValueError:
continue
yield _Cell(tc.top_tc, self)
return list(iter_column_cells())
```
#### cell_margins
```
cell_margins() -> _TableCellMargins
```
The default cell margins for every cell of this table:
```
table.cell_margins.left = Pt(6)
```
An edge reads `None` when the table sets no value for it, in which case the table style's margin applies. Assigning `None` removes the override.
Source code in `src/docx/table.py`
```
@lazyproperty
def cell_margins(self) -> _TableCellMargins:
"""The default cell margins for every cell of this table::
table.cell_margins.left = Pt(6)
An edge reads |None| when the table sets no value for it, in which case the
table style's margin applies. Assigning |None| removes the override.
"""
return _TableCellMargins(self._tbl)
```
#### look
```
look() -> _TableLook
```
Which parts of the table style apply to this table:
```
table.look.first_row = True
table.look.horizontal_banding = True
```
Applying a table style without setting these produces a table that looks nothing like the style preview in Word.
Source code in `src/docx/table.py`
```
@lazyproperty
def look(self) -> _TableLook:
"""Which parts of the table style apply to this table::
table.look.first_row = True
table.look.horizontal_banding = True
Applying a table style without setting these produces a table that looks nothing
like the style preview in Word.
"""
return _TableLook(self._tbl)
```
#### copy_to
```
copy_to(
container: BlockItemContainer | Document,
*,
before: Paragraph | Table | None = None,
after: Paragraph | Table | None = None,
missing_style: str = "copy",
) -> Table
```
Return a copy of this table, newly placed in `container`:
```
new_table = table.copy_to(document)
```
See Paragraph.copy_to for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles and numbering.
Source code in `src/docx/table.py`
```
def copy_to(
self,
container: BlockItemContainer | Document,
*,
before: Paragraph | Table | None = None,
after: Paragraph | Table | None = None,
missing_style: str = "copy",
) -> Table:
"""Return a copy of this table, newly placed in `container`::
new_table = table.copy_to(document)
See :meth:`.Paragraph.copy_to` for what is repaired on the way — relationships,
drawing ids, bookmarks, and, for a copy into another document, styles and
numbering.
"""
from docx.copy import copy_content, destination_for, place
dest_part, dest_element = destination_for(container)
new_tbl = copy_content(self._tbl, self.part, dest_part, missing_style=missing_style)
place(new_tbl, dest_element, before, after)
return Table(new_tbl, container) # pyright: ignore[reportArgumentType]
```
#### delete
```
delete() -> None
```
Remove this table from the document.
Relationships referenced only from inside the table are dropped, and any range marker left unmatched is removed, as for `Paragraph.delete()`.
Source code in `src/docx/table.py`
```
def delete(self) -> None:
"""Remove this table from the document.
Relationships referenced only from inside the table are dropped, and any range
marker left unmatched is removed, as for `Paragraph.delete()`.
"""
delete_element(self._tbl, self.part)
```
#### columns
```
columns()
```
\_Columns instance representing the sequence of columns in this table.
Source code in `src/docx/table.py`
```
@lazyproperty
def columns(self):
"""|_Columns| instance representing the sequence of columns in this table."""
return _Columns(self._tbl, self)
```
#### row_cells
```
row_cells(row_idx: int) -> list[_Cell]
```
DEPRECATED: Use `table.rows[row_idx].cells` instead.
Sequence of cells in the row at `row_idx` in this table.
Source code in `src/docx/table.py`
```
def row_cells(self, row_idx: int) -> list[_Cell]:
"""DEPRECATED: Use `table.rows[row_idx].cells` instead.
Sequence of cells in the row at `row_idx` in this table.
"""
column_count = self._column_count
start = row_idx * column_count
end = start + column_count
return self._cells[start:end]
```
#### rows
```
rows() -> _Rows
```
\_Rows instance containing the sequence of rows in this table.
Source code in `src/docx/table.py`
```
@lazyproperty
def rows(self) -> _Rows:
"""|_Rows| instance containing the sequence of rows in this table."""
return _Rows(self._tbl, self)
```
### \_Cell
```
_Cell(tc: CT_Tc, parent: TableParent)
```
Bases: `BlockItemContainer`
Table cell.
Source code in `src/docx/table.py`
```
def __init__(self, tc: CT_Tc, parent: TableParent):
super(_Cell, self).__init__(tc, cast("t.ProvidesStoryPart", parent))
self._parent = parent
self._tc = self._element = tc
```
#### column_index
```
column_index: int
```
Index of the left-most layout-grid column this cell occupies.
Together with `.row_index` this gives the origin of the cell, which is what tells a repeat of a merged cell apart from a cell in its own right:
```
for row_idx, row in enumerate(table.rows):
for col_idx, cell in enumerate(row.cells):
if (cell.row_index, cell.column_index) != (row_idx, col_idx):
continue # -- already emitted, this is part of a merged cell --
emit(cell.text, rowspan=cell.span_height, colspan=cell.grid_span)
```
Note this is a layout-grid column index, so it accounts for the grid positions a row leaves unpopulated at its start; see `_Row.grid_cols_before`.
#### grid_span
```
grid_span: int
```
Number of layout-grid cells this cell spans horizontally.
A "normal" cell has a grid-span of 1. A horizontally merged cell has a grid-span of 2 or more.
#### is_merged
```
is_merged: bool
```
`True` when this cell spans more than one layout-grid cell.
Horizontally, vertically, or both.
#### paragraphs
```
paragraphs
```
List of paragraphs in the cell.
A table cell is required to contain at least one block-level element and end with a paragraph. By default, a new cell contains a single paragraph. Read-only
#### row_index
```
row_index: int
```
Index of the top-most row this cell occupies.
For a vertically merged cell this is the row the merge starts at, not the row the cell was reached through. See `.column_index` for how the pair is used.
#### span
```
span: tuple[int, int]
```
The extent of this cell as `(rows, columns)`.
`(1, 1)` for an unmerged cell.
#### span_height
```
span_height: int
```
Number of rows this cell spans vertically.
An unmerged cell has a span-height of 1; a vertically merged cell has 2 or more. This is the read-side counterpart of `.grid_span`, and the two together describe a merge completely, including the combined case of a cell that is merged in both directions.
A merge is measured by following its continuation cells, so a document whose origin cell omits `w:vMerge` — legal in practice and common from generators other than Word — reports the same extent Word renders.
#### tables
```
tables
```
List of tables in the cell, in the order they appear.
Read-only.
#### text
```
text: str
```
The entire contents of this cell as a string of text.
Assigning a string to this property replaces all existing content with a single paragraph containing the assigned text in a single run.
#### text_direction
```
text_direction: WD_TEXT_DIRECTION | None
```
Flow direction of the text in this cell, or `None` when inherited.
This is what a rotated header cell needs:
```
cell.text_direction = WD_TEXT_DIRECTION.BT_LR
```
Assigning `None` removes the setting, restoring inheritance.
#### vertical_alignment
```
vertical_alignment
```
Member of WdCellVerticalAlignment or None.
A value of `None` indicates vertical alignment for this cell is inherited. Assigning `None` causes any explicitly defined vertical alignment to be removed, restoring inheritance.
#### width
```
width
```
The width of this cell in EMU, or `None` if no explicit width is set.
#### add_paragraph
```
add_paragraph(
text: str = "",
style: str | ParagraphStyle | None = None,
)
```
Return a paragraph newly added to the end of the content in this cell.
If present, `text` is added to the paragraph in a single run. If specified, the paragraph style `style` is applied. If `style` is not specified or is `None`, the result is as though the 'Normal' style was applied. Note that the formatting of text in a cell can be influenced by the table style. `text` can contain tab (`\t`) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (`\n`) or carriage return (`\r`) characters, each of which is converted to a line break.
Source code in `src/docx/table.py`
```
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None):
"""Return a paragraph newly added to the end of the content in this cell.
If present, `text` is added to the paragraph in a single run. If specified, the
paragraph style `style` is applied. If `style` is not specified or is |None|,
the result is as though the 'Normal' style was applied. Note that the formatting
of text in a cell can be influenced by the table style. `text` can contain tab
(``\\t``) characters, which are converted to the appropriate XML form for a tab.
`text` can also include newline (``\\n``) or carriage return (``\\r``)
characters, each of which is converted to a line break.
"""
return super(_Cell, self).add_paragraph(text, style)
```
#### add_table
```
add_table(
rows: int,
cols: int,
*,
title: str | None = None,
description: str | None = None,
) -> Table
```
Return a table newly added to this cell after any existing cell content.
The new table will have `rows` rows and `cols` columns.
An empty paragraph is added after the table because Word requires a paragraph element as the last element in every cell.
`description` is the table's alternative text and `title` the separate, caption-like field Word writes alongside it. Both are omitted from the XML when `None`.
Source code in `src/docx/table.py`
```
def add_table( # pyright: ignore[reportIncompatibleMethodOverride]
self,
rows: int,
cols: int,
*,
title: str | None = None,
description: str | None = None,
) -> Table:
"""Return a table newly added to this cell after any existing cell content.
The new table will have `rows` rows and `cols` columns.
An empty paragraph is added after the table because Word requires a paragraph
element as the last element in every cell.
`description` is the table's alternative text and `title` the separate,
caption-like field Word writes alongside it. Both are omitted from the XML when
|None|.
"""
width = self.width if self.width is not None else Inches(1)
table = super(_Cell, self).add_table(
rows, cols, width, title=title, description=description
)
self.add_paragraph()
return table
```
#### add_caption
```
add_caption(
label: str,
text: str = "",
*,
style: str | None = "Caption",
separator: str = " ",
restart_at_heading_level: int | None = None,
before: Paragraph | None = None,
) -> Caption
```
Add a numbered, cross-referenceable caption to this cell and return it.
See Document.add_caption.
Source code in `src/docx/table.py`
```
def add_caption(
self,
label: str,
text: str = "",
*,
style: str | None = "Caption",
separator: str = " ",
restart_at_heading_level: int | None = None,
before: Paragraph | None = None,
) -> Caption:
"""Add a numbered, cross-referenceable caption to this cell and return it.
See :meth:`.Document.add_caption`.
"""
from docx.caption import add_caption
return add_caption(
self,
label,
text,
style=style,
separator=separator,
restart_at_heading_level=restart_at_heading_level,
before=before,
)
```
#### borders
```
borders() -> _CellBorders
```
The border edges of this cell, as a mapping keyed by edge name:
```
cell.borders["bottom"].line = WD_LINE_STYLE.DOUBLE
```
A cell adds the two diagonal edges `tl2br` and `tr2bl` to the edges a table admits. A border set here takes precedence over the table border at the same edge.
Source code in `src/docx/table.py`
```
@lazyproperty
def borders(self) -> _CellBorders:
"""The border edges of this cell, as a mapping keyed by edge name::
cell.borders["bottom"].line = WD_LINE_STYLE.DOUBLE
A cell adds the two diagonal edges `tl2br` and `tr2bl` to the edges a table
admits. A border set here takes precedence over the table border at the same
edge.
"""
return _CellBorders(self._tc)
```
#### merge
```
merge(other_cell: _Cell)
```
Return a merged cell created by spanning the rectangular region having this cell and `other_cell` as diagonal corners.
Raises InvalidSpanError if the cells do not define a rectangular region.
Source code in `src/docx/table.py`
```
def merge(self, other_cell: _Cell):
"""Return a merged cell created by spanning the rectangular region having this
cell and `other_cell` as diagonal corners.
Raises |InvalidSpanError| if the cells do not define a rectangular region.
"""
tc, tc_2 = self._tc, other_cell._tc
merged_tc = tc.merge(tc_2)
return _Cell(merged_tc, self._parent)
```
### \_Column
```
_Column(gridCol: CT_TblGridCol, parent: TableParent)
```
Bases: `Parented`
Table column.
Source code in `src/docx/table.py`
```
def __init__(self, gridCol: CT_TblGridCol, parent: TableParent):
super(_Column, self).__init__(parent)
self._parent = parent
self._gridCol = gridCol
```
#### cells
```
cells: tuple[_Cell, ...]
```
Sequence of \_Cell instances corresponding to cells in this column.
#### table
```
table: Table
```
Reference to the Table object this column belongs to.
#### width
```
width: Length | None
```
The width of this column in EMU, or `None` if no explicit width is set.
#### delete
```
delete() -> None
```
Remove this column from its table.
Removes the `w:gridCol` and the cell occupying this layout-grid column in every row. A cell that spans this column and others is narrowed by one rather than removed, so the rest of its span survives.
Source code in `src/docx/table.py`
```
def delete(self) -> None:
"""Remove this column from its table.
Removes the `w:gridCol` and the cell occupying this layout-grid column in every
row. A cell that spans this column and others is narrowed by one rather than
removed, so the rest of its span survives.
"""
table = self.table
column_idx = self._index
for tr in table._tbl.tr_lst: # pyright: ignore[reportPrivateUsage]
tr.delete_grid_column(column_idx, table.part)
delete_element(self._gridCol, table.part)
```
### \_Columns
```
_Columns(tbl: CT_Tbl, parent: TableParent)
```
Bases: `Parented`
Sequence of \_Column instances corresponding to the columns in a table.
Supports `len()`, iteration and indexed access.
Source code in `src/docx/table.py`
```
def __init__(self, tbl: CT_Tbl, parent: TableParent):
super(_Columns, self).__init__(parent)
self._parent = parent
self._tbl = tbl
```
#### table
```
table: Table
```
Reference to the Table object this column collection belongs to.
### \_Row
```
_Row(tr: CT_Row, parent: TableParent)
```
Bases: `Parented`
Table row.
Source code in `src/docx/table.py`
```
def __init__(self, tr: CT_Row, parent: TableParent):
super(_Row, self).__init__(parent)
self._parent = parent
self._tr = self._element = tr
```
#### cells
```
cells: tuple[_Cell, ...]
```
Sequence of \_Cell instances corresponding to cells in this row.
Note that Word allows table rows to start later than the first column and end before the last column.
- Only cells actually present are included in the return value.
- This implies the length of this cell sequence may differ between rows of the same table.
- If you are reading the cells from each row to form a rectangular "matrix" data structure of the table cell values, you will need to account for empty leading and/or trailing layout-grid positions using `.grid_cols_before` and `.grid_cols_after`.
#### grid_cols_after
```
grid_cols_after: int
```
Count of unpopulated grid-columns after the last cell in this row.
Word allows a row to "end early", meaning that one or more cells are not present at the end of that row.
Note these are not simply "empty" cells. The renderer reads this value and "skips" this many columns after drawing the last cell.
Note this also implies that not all rows are guaranteed to have the same number of cells, e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same table. Visually this appears as a column (at the beginning or end, not in the middle) with one or more cells missing.
#### grid_cols_before
```
grid_cols_before: int
```
Count of unpopulated grid-columns before the first cell in this row.
Word allows a row to "start late", meaning that one or more cells are not present at the beginning of that row.
Note these are not simply "empty" cells. The renderer reads this value and skips forward to the table layout-grid position of the first cell in this row; the renderer "skips" this many columns before drawing the first cell.
Note this also implies that not all rows are guaranteed to have the same number of cells, e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same table.
#### height
```
height: Length | None
```
Return a Length object representing the height of this cell, or `None` if no explicit height is set.
#### dont_split
```
dont_split: bool | None
```
`True` if this row is kept on a single page rather than broken across pages.
Corresponds to unchecking "Allow row to break across pages" in Word. `None` indicates no explicit setting, which Word treats as allowing the break.
#### repeat_as_header
```
repeat_as_header: bool | None
```
`True` when this row repeats at the top of each page the table spans.
Corresponds to "Repeat Header Rows" in Word. `None` indicates no explicit setting, which Word treats as off.
Word only honours this on a contiguous run of rows starting at the first row of the table. Setting it on row 3 alone is legal XML that has no visible effect.
#### hidden
```
hidden: bool | None
```
`True` when this row is not displayed.
`None` indicates no explicit setting, which Word treats as visible.
#### alignment
```
alignment: WD_TABLE_ALIGNMENT | None
```
Horizontal alignment of this row within the table, or `None` if not set.
This overrides the table's own alignment for this row alone.
#### cell_spacing
```
cell_spacing: Length | None
```
Spacing between the cells of this row, or `None` if not set.
#### width_before
```
width_before: Length | None
```
Width of the grid positions this row leaves unpopulated at its start.
Pairs with `.grid_cols_before`, which counts them. `None` if not set.
#### width_after
```
width_after: Length | None
```
Width of the grid positions this row leaves unpopulated at its end.
Pairs with `.grid_cols_after`, which counts them. `None` if not set.
#### height_rule
```
height_rule: WD_ROW_HEIGHT_RULE | None
```
Return the height rule of this cell as a member of the WdRowHeightRule.
This value is `None` if no explicit height_rule is set.
#### table
```
table: Table
```
Reference to the Table object this row belongs to.
#### copy_to
```
copy_to(
table: Table,
*,
before: _Row | None = None,
after: _Row | None = None,
missing_style: str = "copy",
) -> _Row
```
Return a copy of this row, newly placed in `table`.
"Duplicate this table row N times" is the other most-written-by-hand operation:
```
for _ in range(9):
template_row.copy_to(table)
```
`before` and `after` place the copy relative to an existing row; with neither it is appended.
The copy keeps this row's own cell widths and spans. It is not adjusted to `table`'s grid, so copying a row into a table of a different column count produces a row that does not line up — which is what the XML says and what Word will render.
See Paragraph.copy_to for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles and numbering.
Source code in `src/docx/table.py`
```
def copy_to(
self,
table: Table,
*,
before: _Row | None = None,
after: _Row | None = None,
missing_style: str = "copy",
) -> _Row:
"""Return a copy of this row, newly placed in `table`.
"Duplicate this table row N times" is the other most-written-by-hand operation::
for _ in range(9):
template_row.copy_to(table)
`before` and `after` place the copy relative to an existing row; with neither it
is appended.
The copy keeps this row's own cell widths and spans. It is not adjusted to
`table`'s grid, so copying a row into a table of a different column count
produces a row that does not line up — which is what the XML says and what Word
will render.
See :meth:`.Paragraph.copy_to` for what is repaired on the way — relationships,
drawing ids, bookmarks, and, for a copy into another document, styles and
numbering.
"""
from docx.copy import copy_content
dest_part = table.part
new_tr = copy_content(self._tr, self.part, dest_part, missing_style=missing_style)
if before is not None and after is not None:
raise ValueError("pass at most one of `before` and `after`")
if before is not None:
before._tr.addprevious(new_tr)
elif after is not None:
after._tr.addnext(new_tr)
else:
table._tbl.append(new_tr)
return _Row(new_tr, table) # pyright: ignore[reportArgumentType]
```
#### delete
```
delete() -> None
```
Remove this row from its table.
A vertically merged cell whose span started in this row is not dropped: the row below inherits it, so the merge continues to render, which is what Word does when a row is deleted.
Source code in `src/docx/table.py`
```
def delete(self) -> None:
"""Remove this row from its table.
A vertically merged cell whose span started in this row is not dropped: the row
below inherits it, so the merge continues to render, which is what Word does
when a row is deleted.
"""
self._tr.transfer_vertical_spans_to_row_below()
delete_element(self._tr, self.table.part)
```
### \_Rows
```
_Rows(tbl: CT_Tbl, parent: TableParent)
```
Bases: `Parented`
Sequence of \_Row objects corresponding to the rows in a table.
Supports `len()`, iteration, indexed access, and slicing.
Source code in `src/docx/table.py`
```
def __init__(self, tbl: CT_Tbl, parent: TableParent):
super(_Rows, self).__init__(parent)
self._parent = parent
self._tbl = tbl
```
#### table
```
table: Table
```
Reference to the Table object this row collection belongs to.
## theme
The Theme object, the document's theme fonts and colours.
### \_ThemeFont
```
_ThemeFont(fontCollection: object)
```
One font collection of a theme — its major or minor fonts.
Source code in `src/docx/theme.py`
```
def __init__(self, fontCollection: object):
self._fontCollection = fontCollection
```
#### latin
```
latin: str | None
```
The Latin typeface of this collection, e.g. `"Calibri"`.
This is what a `minorHAnsi` or `majorHAnsi` theme token resolves to, and the one that matters for a Western document.
#### east_asian
```
east_asian: str | None
```
The East Asian typeface of this collection, or `None` when it sets none.
The default Office theme leaves this empty and relies on the `a:font` script entries instead, so `None` here is ordinary rather than exceptional.
#### complex_script
```
complex_script: str | None
```
The complex-script typeface of this collection, or `None` when it sets none.
### Theme
```
Theme(
theme: CT_OfficeStyleSheet, part: XmlPart | None = None
)
```
Bases: `ElementProxy`
The document's theme: its major and minor fonts and its twelve theme colours.
Reached through Document.theme, which is `None` for a document carrying no theme part.
The point of exposing it is Font.theme_typeface: a run whose font is set only by a theme token reports `None` for Font.name, and this is where the concrete typeface behind that token lives.
Source code in `src/docx/theme.py`
```
def __init__(self, theme: CT_OfficeStyleSheet, part: XmlPart | None = None):
super().__init__(theme, part) # pyright: ignore[reportArgumentType]
self._element = theme
```
#### name
```
name: str | None
```
The theme's name, e.g. `"Office Theme"`, or `None` when it has none.
#### major_font
```
major_font: _ThemeFont
```
The theme's major fonts, which Word applies to headings.
#### minor_font
```
minor_font: _ThemeFont
```
The theme's minor fonts, which Word applies to body text.
#### colors
```
colors: dict[str, RGBColor | str | None]
```
The twelve theme colours, keyed by slot name, in schema order.
#### typeface
```
typeface(theme_token: str) -> str | None
```
The concrete typeface `theme_token` names, or `None` when there is none.
`theme_token` is a `w:rFonts/@w:asciiTheme`-style value such as `"minorHAnsi"`. An unrecognised token, and a token whose slot the theme leaves empty, both give `None`.
Source code in `src/docx/theme.py`
```
def typeface(self, theme_token: str) -> str | None:
"""The concrete typeface `theme_token` names, or |None| when there is none.
`theme_token` is a `w:rFonts/@w:asciiTheme`-style value such as ``"minorHAnsi"``.
An unrecognised token, and a token whose slot the theme leaves empty, both give
|None|.
"""
try:
collection, script = _THEME_TOKENS[theme_token]
except KeyError:
return None
font = self.major_font if collection == "major" else self.minor_font
return {"latin": font.latin, "ea": font.east_asian, "cs": font.complex_script}[script]
```
#### color
```
color(name: str) -> RGBColor | str | None
```
The RGB value of theme colour `name`, or `None` when the theme has none.
`name` is one of `dk1`, `lt1`, `dk2`, `lt2`, `accent1` through `accent6`, `hlink` and `folHlink` — the slot names as they appear in the XML. A MSO_THEME_COLOR member's own spelling differs; this takes the XML one because that is what the theme part is keyed on.
A system colour such as `dk1` reports the RGB value the producing application last resolved it to, which is the only concrete value available outside that operating system.
Source code in `src/docx/theme.py`
```
def color(self, name: str) -> RGBColor | str | None:
"""The RGB value of theme colour `name`, or |None| when the theme has none.
`name` is one of ``dk1``, ``lt1``, ``dk2``, ``lt2``, ``accent1`` through
``accent6``, ``hlink`` and ``folHlink`` — the slot names as they appear in the
XML. A |MSO_THEME_COLOR| member's own spelling differs; this takes the XML one
because that is what the theme part is keyed on.
A system colour such as ``dk1`` reports the RGB value the producing application
last resolved it to, which is the only concrete value available outside that
operating system.
"""
clrScheme = self._element.themeElements.clrScheme
if name not in clrScheme.slots:
raise ValueError(
"no theme color %r; must be one of %s" % (name, ", ".join(clrScheme.slots))
)
color = clrScheme.color(name)
return None if color is None else color.rgb
```
## types
Abstract types used by `python-docx`.
### ProvidesStoryPart
Bases: `Protocol`
An object that provides access to the StoryPart.
This type is for objects that have a story part like document or header as their root part.
### ProvidesXmlPart
Bases: `Protocol`
An object that provides access to its XmlPart.
This type is for objects that need access to their part but it either isn't a StoryPart or they don't care, possibly because they just need access to the package or related parts.
## 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
```
is_image: bool
```
True when this is an image watermark rather than a text one.
#### text
```
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
```
## dml
## color
DrawingML objects related to color, ColorFormat being the most prominent.
### ColorFormat
```
ColorFormat(rPr_parent: RPrParent)
```
Bases: `ElementProxy`
Provides access to color settings like RGB color, theme color, and luminance adjustments.
Source code in `src/docx/dml/color.py`
```
def __init__(self, rPr_parent: RPrParent):
super(ColorFormat, self).__init__(rPr_parent)
self._element = rPr_parent
```
#### rgb
```
rgb: RGBColor | None
```
An RGBColor value or `None` if no RGB color is specified.
When type is `MSO_COLOR_TYPE.RGB`, the value of this property will always be an RGBColor value. It may also be an RGBColor value if type is `MSO_COLOR_TYPE.THEME`, as Word writes the current value of a theme color when one is assigned. In that case, the RGB value should be interpreted as no more than a good guess however, as the theme color takes precedence at rendering time. Its value is `None` whenever type is either `None` or `MSO_COLOR_TYPE.AUTO`.
Assigning an RGBColor value causes type to become `MSO_COLOR_TYPE.RGB` and any theme color is removed. Assigning `None` causes any color to be removed such that the effective color is inherited from the style hierarchy.
#### theme_color
```
theme_color: MSO_THEME_COLOR | None
```
Member of MsoThemeColorIndex or `None` if no theme color is specified.
When type is `MSO_COLOR_TYPE.THEME`, the value of this property will always be a member of MsoThemeColorIndex. When type has any other value, the value of this property is `None`.
Assigning a member of MsoThemeColorIndex causes type to become `MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word. Assigning `None` causes any color specification to be removed such that the effective color is inherited from the style hierarchy.
#### type
```
type: MSO_COLOR_TYPE | None
```
Read-only.
A member of MsoColorType, one of RGB, THEME, or AUTO, corresponding to the way this color is defined. Its value is `None` if no color is applied at this level, which causes the effective color to be inherited from the style hierarchy.
## drawing
DrawingML-related objects are in this subpackage.
### Drawing
```
Drawing(drawing: CT_Drawing, parent: ProvidesStoryPart)
```
Bases: `Parented`
Container for a DrawingML object.
Source code in `src/docx/drawing/__init__.py`
```
def __init__(self, drawing: CT_Drawing, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._parent = parent
self._drawing = self._element = drawing
```
#### has_picture
```
has_picture: bool
```
True when `drawing` contains an embedded picture.
A drawing can contain a picture, but it can also contain a chart, SmartArt, or a drawing canvas. Methods related to a picture, like `.image`, will raise when the drawing does not contain a picture. Use this value to determine whether image methods will succeed.
This value is `False` when a linked picture is present. This should be relatively rare and the image would only be retrievable from the filesystem.
Note this does not distinguish between inline and floating images. The presence of either one will cause this value to be `True`.
#### image
```
image: Image
```
An `Image` proxy object for the image in this (picture) drawing.
Raises `ValueError` when this drawing does contains something other than a picture. Use `.has_picture` to qualify drawing objects before using this property.
## enum
## base
Base classes and other objects used by enumerations.
### BaseEnum
Bases: `int`, `Enum`
Base class for Enums that do not map XML attr values.
The enum's value will be an integer, corresponding to the integer assigned the corresponding member in the MS API enum of the same name.
### BaseXmlEnum
Bases: `int`, `Enum`
Base class for Enums that also map XML attr values.
The enum's value will be an integer, corresponding to the integer assigned the corresponding member in the MS API enum of the same name.
#### from_xml
```
from_xml(xml_value: str | None) -> Self
```
Enumeration member corresponding to XML attribute value `xml_value`.
Example:
```
>>> WD_PARAGRAPH_ALIGNMENT.from_xml("center")
WD_PARAGRAPH_ALIGNMENT.CENTER
```
Source code in `src/docx/enum/base.py`
```
@classmethod
def from_xml(cls, xml_value: str | None) -> Self:
"""Enumeration member corresponding to XML attribute value `xml_value`.
Example::
>>> WD_PARAGRAPH_ALIGNMENT.from_xml("center")
WD_PARAGRAPH_ALIGNMENT.CENTER
"""
member = next((member for member in cls if member.xml_value == xml_value), None)
if member is None:
raise ValueError(f"{cls.__name__} has no XML mapping for '{xml_value}'")
return member
```
#### to_xml
```
to_xml(value: int | _T | None) -> str | None
```
XML value of this enum member, generally an XML attribute value.
Source code in `src/docx/enum/base.py`
```
@classmethod
def to_xml(cls: Type[_T], value: int | _T | None) -> str | None:
"""XML value of this enum member, generally an XML attribute value."""
# -- presence of multi-arg `__new__()` method fools type-checker, but getting a
# -- member by its value using EnumCls(val) works as usual.
member = cls(value)
xml_value = member.xml_value
if not xml_value:
raise ValueError(f"{cls.__name__}.{member.name} has no XML representation")
return xml_value
```
### DocsPageFormatter
```
DocsPageFormatter(clsname: str, clsdict: Dict[str, Any])
```
Generate an .rst doc page for an enumeration.
Formats a RestructuredText documention page (string) for the enumeration class parts passed to the constructor. An immutable one-shot service object.
Source code in `src/docx/enum/base.py`
```
def __init__(self, clsname: str, clsdict: Dict[str, Any]):
self._clsname = clsname
self._clsdict = clsdict
```
#### page_str
```
page_str
```
The RestructuredText documentation page for the enumeration.
This is the only API member for the class.
## dml
Enumerations used by DrawingML objects.
### MSO_COLOR_TYPE
Bases: `BaseEnum`
Specifies the color specification scheme.
Example:
```
from docx.enum.dml import MSO_COLOR_TYPE
assert font.color.type == MSO_COLOR_TYPE.SCHEME
```
MS API name: `MsoColorType`
http://msdn.microsoft.com/en-us/library/office/ff864912(v=office.15).aspx
#### RGB
```
RGB = (1, 'Color is specified by an |RGBColor| value.')
```
Color is specified by an RGBColor value.
#### THEME
```
THEME = (2, 'Color is one of the preset theme colors.')
```
Color is one of the preset theme colors.
#### AUTO
```
AUTO = (
101,
"Color is determined automatically by the application.",
)
```
Color is determined automatically by the application.
### MSO_THEME_COLOR_INDEX
Bases: `BaseXmlEnum`
Indicates the Office theme color, one of those shown in the color gallery on the formatting ribbon.
Alias: `MSO_THEME_COLOR`
Example:
```
from docx.enum.dml import MSO_THEME_COLOR
font.color.theme_color = MSO_THEME_COLOR.ACCENT_1
```
MS API name: `MsoThemeColorIndex`
http://msdn.microsoft.com/en-us/library/office/ff860782(v=office.15).aspx
#### NOT_THEME_COLOR
```
NOT_THEME_COLOR = (
0,
"UNMAPPED",
"Indicates the color is not a theme color.",
)
```
Indicates the color is not a theme color.
#### ACCENT_1
```
ACCENT_1 = (
5,
"accent1",
"Specifies the Accent 1 theme color.",
)
```
Specifies the Accent 1 theme color.
#### ACCENT_2
```
ACCENT_2 = (
6,
"accent2",
"Specifies the Accent 2 theme color.",
)
```
Specifies the Accent 2 theme color.
#### ACCENT_3
```
ACCENT_3 = (
7,
"accent3",
"Specifies the Accent 3 theme color.",
)
```
Specifies the Accent 3 theme color.
#### ACCENT_4
```
ACCENT_4 = (
8,
"accent4",
"Specifies the Accent 4 theme color.",
)
```
Specifies the Accent 4 theme color.
#### ACCENT_5
```
ACCENT_5 = (
9,
"accent5",
"Specifies the Accent 5 theme color.",
)
```
Specifies the Accent 5 theme color.
#### ACCENT_6
```
ACCENT_6 = (
10,
"accent6",
"Specifies the Accent 6 theme color.",
)
```
Specifies the Accent 6 theme color.
#### BACKGROUND_1
```
BACKGROUND_1 = (
14,
"background1",
"Specifies the Background 1 theme color.",
)
```
Specifies the Background 1 theme color.
#### BACKGROUND_2
```
BACKGROUND_2 = (
16,
"background2",
"Specifies the Background 2 theme color.",
)
```
Specifies the Background 2 theme color.
#### DARK_1
```
DARK_1 = (1, 'dark1', 'Specifies the Dark 1 theme color.')
```
Specifies the Dark 1 theme color.
#### DARK_2
```
DARK_2 = (3, 'dark2', 'Specifies the Dark 2 theme color.')
```
Specifies the Dark 2 theme color.
#### FOLLOWED_HYPERLINK
```
FOLLOWED_HYPERLINK = (
12,
"followedHyperlink",
"Specifies the theme color for a clicked hyperlink.",
)
```
Specifies the theme color for a clicked hyperlink.
#### HYPERLINK
```
HYPERLINK = (
11,
"hyperlink",
"Specifies the theme color for a hyperlink.",
)
```
Specifies the theme color for a hyperlink.
#### LIGHT_1
```
LIGHT_1 = (
2,
"light1",
"Specifies the Light 1 theme color.",
)
```
Specifies the Light 1 theme color.
#### LIGHT_2
```
LIGHT_2 = (
4,
"light2",
"Specifies the Light 2 theme color.",
)
```
Specifies the Light 2 theme color.
#### TEXT_1
```
TEXT_1 = (13, 'text1', 'Specifies the Text 1 theme color.')
```
Specifies the Text 1 theme color.
#### TEXT_2
```
TEXT_2 = (15, 'text2', 'Specifies the Text 2 theme color.')
```
Specifies the Text 2 theme color.
## numbering
Enumerations related to list numbering in WordprocessingML files.
### WD_NUMBER_FORMAT
Bases: `BaseXmlEnum`
Specifies how a numbering level renders its counter.
Corresponds to `ST_NumberFormat` in ISO/IEC 29500 §17.18.59. The specification defines a long tail of locale-specific formats; the ones this library can actually render a number for are marked below. For the rest, and for any value not listed here at all, NumberingLevel.format_number falls back to decimal — a wrong number is less useful than an obviously plain one, but the alternative of raising would make a document using Japanese counting unreadable rather than imperfect.
MS API name: `WdListNumberStyle` (the closest equivalent; the mapping is not exact)
#### DECIMAL
```
DECIMAL = (
0,
"decimal",
"Arabic numerals: 1, 2, 3. Rendered.",
)
```
Arabic numerals: 1, 2, 3.
#### UPPER_ROMAN
```
UPPER_ROMAN = (
1,
"upperRoman",
"Upper-case Roman numerals: I, II, III. Rendered.",
)
```
Upper-case Roman numerals: I, II, III.
#### LOWER_ROMAN
```
LOWER_ROMAN = (
2,
"lowerRoman",
"Lower-case Roman numerals: i, ii, iii. Rendered.",
)
```
Lower-case Roman numerals: i, ii, iii.
#### UPPER_LETTER
```
UPPER_LETTER = (
3,
"upperLetter",
"Upper-case letters: A, B, C, continuing AA, BB, CC after Z. Rendered.",
)
```
Upper-case letters: A, B, C, continuing AA, BB, CC after Z.
#### LOWER_LETTER
```
LOWER_LETTER = (
4,
"lowerLetter",
"Lower-case letters: a, b, c, continuing aa, bb, cc after z. Rendered.",
)
```
Lower-case letters: a, b, c, continuing aa, bb, cc after z.
#### ORDINAL
```
ORDINAL = (
5,
"ordinal",
"Ordinal numerals: 1st, 2nd, 3rd. Rendered.",
)
```
Ordinal numerals: 1st, 2nd, 3rd.
#### CARDINAL_TEXT
```
CARDINAL_TEXT = (
6,
"cardinalText",
"Cardinal text: One, Two, Three. Not rendered.",
)
```
Cardinal text: One, Two, Three.
#### ORDINAL_TEXT
```
ORDINAL_TEXT = (
7,
"ordinalText",
"Ordinal text: First, Second, Third. Not rendered.",
)
```
Ordinal text: First, Second, Third.
#### HEX
```
HEX = (
8,
"hex",
"Hexadecimal numerals: 8, 9, A, B. Rendered.",
)
```
Hexadecimal numerals: 8, 9, A, B.
#### CHICAGO
```
CHICAGO = (
9,
"chicago",
"The Chicago Manual of Style sequence of footnote marks: *, †, ‡, §. Rendered.",
)
```
The Chicago Manual of Style footnote marks.
#### DECIMAL_ZERO
```
DECIMAL_ZERO = (
10,
"decimalZero",
"Arabic numerals with a leading zero below ten: 01, 02, 03. Rendered.",
)
```
Arabic numerals with a leading zero below ten: 01, 02, 03.
#### BULLET
```
BULLET = (
11,
"bullet",
"A bullet rather than a number. The character shown is the literal level text, so the counter is not rendered at all.",
)
```
A bullet rather than a number; the level text is shown literally.
#### NONE
```
NONE = (12, "none", "No numbering is shown for this level.")
```
No numbering is shown for this level.
#### RUSSIAN_LOWER
```
RUSSIAN_LOWER = (
13,
"russianLower",
"Lower-case Cyrillic letters. Not rendered.",
)
```
Lower-case Cyrillic letters.
#### RUSSIAN_UPPER
```
RUSSIAN_UPPER = (
14,
"russianUpper",
"Upper-case Cyrillic letters. Not rendered.",
)
```
Upper-case Cyrillic letters.
#### IDEOGRAPH_DIGITAL
```
IDEOGRAPH_DIGITAL = (
15,
"ideographDigital",
"Chinese numerals. Not rendered.",
)
```
Chinese numerals.
#### JAPANESE_COUNTING
```
JAPANESE_COUNTING = (
16,
"japaneseCounting",
"Japanese counting. Not rendered.",
)
```
Japanese counting.
#### AIUEO
```
AIUEO = (
17,
"aiueo",
"Japanese aiueo ordering. Not rendered.",
)
```
Japanese aiueo ordering.
#### IROHA
```
IROHA = (
18,
"iroha",
"Japanese iroha ordering. Not rendered.",
)
```
Japanese iroha ordering.
#### DECIMAL_FULL_WIDTH
```
DECIMAL_FULL_WIDTH = (
19,
"decimalFullWidth",
"Full-width Arabic numerals. Not rendered.",
)
```
Full-width Arabic numerals.
#### DECIMAL_HALF_WIDTH
```
DECIMAL_HALF_WIDTH = (
20,
"decimalHalfWidth",
"Half-width Arabic numerals. Rendered.",
)
```
Half-width Arabic numerals.
#### GANADA
```
GANADA = (
21,
"ganada",
"Korean ganada ordering. Not rendered.",
)
```
Korean ganada ordering.
#### CHOSUNG
```
CHOSUNG = (
22,
"chosung",
"Korean chosung ordering. Not rendered.",
)
```
Korean chosung ordering.
## revision
Enumerations related to tracked changes in WordprocessingML files.
### WD_REVISION_TYPE
Bases: `BaseXmlEnum`
Specifies what kind of change a tracked revision records.
Example:
```
from docx.enum.revision import WD_REVISION_TYPE
for revision in document.revisions:
if revision.type == WD_REVISION_TYPE.DELETION:
revision.reject()
```
MS API name: `WdRevisionType`
https://learn.microsoft.com/en-us/office/vba/api/word.wdrevisiontype
#### INSERTION
```
INSERTION = (1, 'ins', 'Content was added.')
```
Content was added.
#### DELETION
```
DELETION = (2, 'del', 'Content was removed.')
```
Content was removed.
#### MOVE_FROM
```
MOVE_FROM = (
5,
"moveFrom",
"Content was moved away from here. The deletion half of a move; the matching MOVE_TO holds the same content where it now is.",
)
```
Content was moved away from here — the deletion half of a move.
#### MOVE_TO
```
MOVE_TO = (
6,
"moveTo",
"Content was moved to here. The insertion half of a move.",
)
```
Content was moved to here — the insertion half of a move.
#### FORMATTING
```
FORMATTING = (
3,
None,
"Formatting was changed. The revision records the properties as they were before, which is what rejecting it puts back. Covers `w:rPrChange`, `w:pPrChange` and the other `*Change` elements, which differ only in which properties they record.",
)
```
Formatting was changed; the revision records the previous properties.
## section
Enumerations related to the main document in WordprocessingML files.
### WD_HEADER_FOOTER_INDEX
Bases: `BaseXmlEnum`
Alias: **WD_HEADER_FOOTER**
Specifies one of the three possible header/footer definitions for a section.
For internal use only; not part of the python-docx API.
MS API name: `WdHeaderFooterIndex` URL: https://docs.microsoft.com/en-us/office/vba/api/word.wdheaderfooterindex
#### PRIMARY
```
PRIMARY = (
1,
"default",
"Header for odd pages or all if no even header.",
)
```
Header for odd pages or all if no even header.
#### FIRST_PAGE
```
FIRST_PAGE = (
2,
"first",
"Header for first page of section.",
)
```
Header for first page of section.
#### EVEN_PAGE
```
EVEN_PAGE = (
3,
"even",
"Header for even pages of recto/verso section.",
)
```
Header for even pages of recto/verso section.
### WD_ORIENTATION
Bases: `BaseXmlEnum`
Alias: **WD_ORIENT**
Specifies the page layout orientation.
Example:
```
from docx.enum.section import WD_ORIENT
section = document.sections[-1] section.orientation = WD_ORIENT.LANDSCAPE
```
MS API name: `WdOrientation` MS API URL: http://msdn.microsoft.com/en-us/library/office/ff837902.aspx
#### PORTRAIT
```
PORTRAIT = (0, 'portrait', 'Portrait orientation.')
```
Portrait orientation.
#### LANDSCAPE
```
LANDSCAPE = (1, 'landscape', 'Landscape orientation.')
```
Landscape orientation.
### WD_SECTION_START
Bases: `BaseXmlEnum`
Alias: **WD_SECTION**
Specifies the start type of a section break.
Example:
```
from docx.enum.section import WD_SECTION
section = document.sections[0] section.start_type = WD_SECTION.NEW_PAGE
```
MS API name: `WdSectionStart` MS API URL: http://msdn.microsoft.com/en-us/library/office/ff840975.aspx
#### CONTINUOUS
```
CONTINUOUS = (0, 'continuous', 'Continuous section break.')
```
Continuous section break.
#### NEW_COLUMN
```
NEW_COLUMN = (1, 'nextColumn', 'New column section break.')
```
New column section break.
#### NEW_PAGE
```
NEW_PAGE = (2, 'nextPage', 'New page section break.')
```
New page section break.
#### EVEN_PAGE
```
EVEN_PAGE = (3, 'evenPage', 'Even pages section break.')
```
Even pages section break.
#### ODD_PAGE
```
ODD_PAGE = (
4,
"oddPage",
"Section begins on next odd page.",
)
```
Section begins on next odd page.
## shape
Enumerations related to DrawingML shapes in WordprocessingML files.
### WD_INLINE_SHAPE_TYPE
Bases: `Enum`
Corresponds to WdInlineShapeType enumeration.
http://msdn.microsoft.com/en-us/library/office/ff192587.aspx.
### WD_WRAP_TYPE
Bases: `BaseXmlEnum`
Specifies how text wraps around a floating (anchored) shape.
Example:
```
from docx.enum.shape import WD_WRAP_TYPE
shape = paragraph.add_float_picture("logo.png")
shape.wrap_type = WD_WRAP_TYPE.SQUARE
```
MS API name: `WdWrapType`
https://learn.microsoft.com/en-us/office/vba/api/word.wdwraptype
#### NONE
```
NONE = (
3,
"wrapNone",
"Text does not wrap. The shape floats over the text, or behind it when `behind_text` is set, which is how a watermark is placed.",
)
```
Text does not wrap; the shape floats over or behind it.
#### SQUARE
```
SQUARE = (
0,
"wrapSquare",
"Text wraps around the shape's bounding rectangle.",
)
```
Text wraps around the shape's bounding rectangle.
#### TIGHT
```
TIGHT = (
1,
"wrapTight",
"Text wraps around the shape's outline rather than its bounding rectangle, following the wrap polygon Word derives from the image.",
)
```
Text wraps around the shape's outline rather than its bounding rectangle.
#### THROUGH
```
THROUGH = (
2,
"wrapThrough",
"As TIGHT, but text also flows into any open region within the outline.",
)
```
As TIGHT, but text also flows into open regions within the outline.
#### TOP_BOTTOM
```
TOP_BOTTOM = (
4,
"wrapTopAndBottom",
"Text stops above the shape and resumes below it, leaving the sides clear.",
)
```
Text stops above the shape and resumes below it.
### WD_ANCHOR_RELATIVE_FROM_H
Bases: `BaseXmlEnum`
What the horizontal position of a floating shape is measured from.
MS API name: `WdRelativeHorizontalPosition`
#### MARGIN
```
MARGIN = (0, 'margin', 'Relative to the text margin.')
```
Relative to the text margin.
#### PAGE
```
PAGE = (1, 'page', 'Relative to the edge of the page.')
```
Relative to the edge of the page.
#### COLUMN
```
COLUMN = (2, 'column', 'Relative to the text column.')
```
Relative to the text column.
#### CHARACTER
```
CHARACTER = (
3,
"character",
"Relative to the character the anchor sits at.",
)
```
Relative to the character the anchor sits at.
#### LEFT_MARGIN
```
LEFT_MARGIN = (
4,
"leftMargin",
"Relative to the left margin.",
)
```
Relative to the left margin.
#### RIGHT_MARGIN
```
RIGHT_MARGIN = (
5,
"rightMargin",
"Relative to the right margin.",
)
```
Relative to the right margin.
#### INSIDE_MARGIN
```
INSIDE_MARGIN = (
6,
"insideMargin",
"Relative to the inside margin — the left margin on an odd page and the right margin on an even one, in a document laid out for double-sided printing.",
)
```
Relative to the inside margin of a double-sided layout.
#### OUTSIDE_MARGIN
```
OUTSIDE_MARGIN = (
7,
"outsideMargin",
"Relative to the outside margin.",
)
```
Relative to the outside margin of a double-sided layout.
### WD_ANCHOR_RELATIVE_FROM_V
Bases: `BaseXmlEnum`
What the vertical position of a floating shape is measured from.
MS API name: `WdRelativeVerticalPosition`
#### MARGIN
```
MARGIN = (0, 'margin', 'Relative to the text margin.')
```
Relative to the text margin.
#### PAGE
```
PAGE = (1, 'page', 'Relative to the edge of the page.')
```
Relative to the edge of the page.
#### PARAGRAPH
```
PARAGRAPH = (
2,
"paragraph",
"Relative to the paragraph the anchor sits in.",
)
```
Relative to the paragraph the anchor sits in.
#### LINE
```
LINE = (
3,
"line",
"Relative to the line the anchor sits on.",
)
```
Relative to the line the anchor sits on.
#### TOP_MARGIN
```
TOP_MARGIN = (4, "topMargin", "Relative to the top margin.")
```
Relative to the top margin.
#### BOTTOM_MARGIN
```
BOTTOM_MARGIN = (
5,
"bottomMargin",
"Relative to the bottom margin.",
)
```
Relative to the bottom margin.
#### INSIDE_MARGIN
```
INSIDE_MARGIN = (
6,
"insideMargin",
"Relative to the inside margin.",
)
```
Relative to the inside margin of a double-sided layout.
#### OUTSIDE_MARGIN
```
OUTSIDE_MARGIN = (
7,
"outsideMargin",
"Relative to the outside margin.",
)
```
Relative to the outside margin of a double-sided layout.
### WD_ANCHOR_ALIGN_H
Bases: `BaseXmlEnum`
Horizontal alignment of a floating shape within what it is positioned against.
An alternative to an absolute offset: `LEFT` against `WD_ANCHOR_RELATIVE_FROM_H.PAGE` puts the shape at the left edge of the page whatever the page size turns out to be.
#### LEFT
```
LEFT = (0, 'left', 'Aligned to the left edge.')
```
Aligned to the left edge.
#### CENTER
```
CENTER = (1, 'center', 'Centred.')
```
Centred.
#### RIGHT
```
RIGHT = (2, 'right', 'Aligned to the right edge.')
```
Aligned to the right edge.
#### INSIDE
```
INSIDE = (
3,
"inside",
"Aligned to the inside edge of a double-sided layout.",
)
```
Aligned to the inside edge of a double-sided layout.
#### OUTSIDE
```
OUTSIDE = (
4,
"outside",
"Aligned to the outside edge of a double-sided layout.",
)
```
Aligned to the outside edge of a double-sided layout.
### WD_ANCHOR_ALIGN_V
Bases: `BaseXmlEnum`
Vertical alignment of a floating shape within what it is positioned against.
#### TOP
```
TOP = (0, 'top', 'Aligned to the top edge.')
```
Aligned to the top edge.
#### CENTER
```
CENTER = (1, 'center', 'Centred.')
```
Centred.
#### BOTTOM
```
BOTTOM = (2, 'bottom', 'Aligned to the bottom edge.')
```
Aligned to the bottom edge.
#### INSIDE
```
INSIDE = (
3,
"inside",
"Aligned to the inside edge of a double-sided layout.",
)
```
Aligned to the inside edge of a double-sided layout.
#### OUTSIDE
```
OUTSIDE = (
4,
"outside",
"Aligned to the outside edge of a double-sided layout.",
)
```
Aligned to the outside edge of a double-sided layout.
## style
Enumerations related to styles.
### WD_BUILTIN_STYLE
Bases: `BaseEnum`
Alias: **WD_STYLE**
Specifies a built-in Microsoft Word style.
Example:
```
from docx import Document
from docx.enum.style import WD_STYLE
document = Document()
styles = document.styles
style = styles[WD_STYLE.BODY_TEXT]
```
MS API name: `WdBuiltinStyle`
http://msdn.microsoft.com/en-us/library/office/ff835210.aspx
#### BLOCK_QUOTATION
```
BLOCK_QUOTATION = (-85, 'Block Text.')
```
Block Text.
#### BODY_TEXT
```
BODY_TEXT = (-67, 'Body Text.')
```
Body Text.
#### BODY_TEXT_2
```
BODY_TEXT_2 = (-81, 'Body Text 2.')
```
Body Text 2.
#### BODY_TEXT_3
```
BODY_TEXT_3 = (-82, 'Body Text 3.')
```
Body Text 3.
#### BODY_TEXT_FIRST_INDENT
```
BODY_TEXT_FIRST_INDENT = (-78, 'Body Text First Indent.')
```
Body Text First Indent.
#### BODY_TEXT_FIRST_INDENT_2
```
BODY_TEXT_FIRST_INDENT_2 = (
-79,
"Body Text First Indent 2.",
)
```
Body Text First Indent 2.
#### BODY_TEXT_INDENT
```
BODY_TEXT_INDENT = (-68, 'Body Text Indent.')
```
Body Text Indent.
#### BODY_TEXT_INDENT_2
```
BODY_TEXT_INDENT_2 = (-83, 'Body Text Indent 2.')
```
Body Text Indent 2.
#### BODY_TEXT_INDENT_3
```
BODY_TEXT_INDENT_3 = (-84, 'Body Text Indent 3.')
```
Body Text Indent 3.
#### BOOK_TITLE
```
BOOK_TITLE = (-265, 'Book Title.')
```
Book Title.
#### CAPTION
```
CAPTION = (-35, 'Caption.')
```
Caption.
#### CLOSING
```
CLOSING = (-64, 'Closing.')
```
Closing.
#### COMMENT_REFERENCE
```
COMMENT_REFERENCE = (-40, 'Comment Reference.')
```
Comment Reference.
#### COMMENT_TEXT
```
COMMENT_TEXT = (-31, 'Comment Text.')
```
Comment Text.
#### DATE
```
DATE = (-77, 'Date.')
```
Date.
#### DEFAULT_PARAGRAPH_FONT
```
DEFAULT_PARAGRAPH_FONT = (-66, 'Default Paragraph Font.')
```
Default Paragraph Font.
#### EMPHASIS
```
EMPHASIS = (-89, 'Emphasis.')
```
Emphasis.
#### ENDNOTE_REFERENCE
```
ENDNOTE_REFERENCE = (-43, 'Endnote Reference.')
```
Endnote Reference.
#### ENDNOTE_TEXT
```
ENDNOTE_TEXT = (-44, 'Endnote Text.')
```
Endnote Text.
#### ENVELOPE_ADDRESS
```
ENVELOPE_ADDRESS = (-37, 'Envelope Address.')
```
Envelope Address.
#### ENVELOPE_RETURN
```
ENVELOPE_RETURN = (-38, 'Envelope Return.')
```
Envelope Return.
#### FOOTER
```
FOOTER = (-33, 'Footer.')
```
Footer.
#### FOOTNOTE_REFERENCE
```
FOOTNOTE_REFERENCE = (-39, 'Footnote Reference.')
```
Footnote Reference.
#### FOOTNOTE_TEXT
```
FOOTNOTE_TEXT = (-30, 'Footnote Text.')
```
Footnote Text.
#### HEADER
```
HEADER = (-32, 'Header.')
```
Header.
#### HEADING_1
```
HEADING_1 = (-2, 'Heading 1.')
```
Heading 1.
#### HEADING_2
```
HEADING_2 = (-3, 'Heading 2.')
```
Heading 2.
#### HEADING_3
```
HEADING_3 = (-4, 'Heading 3.')
```
Heading 3.
#### HEADING_4
```
HEADING_4 = (-5, 'Heading 4.')
```
Heading 4.
#### HEADING_5
```
HEADING_5 = (-6, 'Heading 5.')
```
Heading 5.
#### HEADING_6
```
HEADING_6 = (-7, 'Heading 6.')
```
Heading 6.
#### HEADING_7
```
HEADING_7 = (-8, 'Heading 7.')
```
Heading 7.
#### HEADING_8
```
HEADING_8 = (-9, 'Heading 8.')
```
Heading 8.
#### HEADING_9
```
HEADING_9 = (-10, 'Heading 9.')
```
Heading 9.
#### HTML_ACRONYM
```
HTML_ACRONYM = (-96, 'HTML Acronym.')
```
HTML Acronym.
#### HTML_ADDRESS
```
HTML_ADDRESS = (-97, 'HTML Address.')
```
HTML Address.
#### HTML_CITE
```
HTML_CITE = (-98, 'HTML Cite.')
```
HTML Cite.
#### HTML_CODE
```
HTML_CODE = (-99, 'HTML Code.')
```
HTML Code.
#### HTML_DFN
```
HTML_DFN = (-100, 'HTML Definition.')
```
HTML Definition.
#### HTML_KBD
```
HTML_KBD = (-101, 'HTML Keyboard.')
```
HTML Keyboard.
#### HTML_NORMAL
```
HTML_NORMAL = (-95, 'Normal (Web).')
```
Normal (Web).
#### HTML_PRE
```
HTML_PRE = (-102, 'HTML Preformatted.')
```
HTML Preformatted.
#### HTML_SAMP
```
HTML_SAMP = (-103, 'HTML Sample.')
```
HTML Sample.
#### HTML_TT
```
HTML_TT = (-104, 'HTML Typewriter.')
```
HTML Typewriter.
#### HTML_VAR
```
HTML_VAR = (-105, 'HTML Variable.')
```
HTML Variable.
#### HYPERLINK
```
HYPERLINK = (-86, 'Hyperlink.')
```
Hyperlink.
#### HYPERLINK_FOLLOWED
```
HYPERLINK_FOLLOWED = (-87, 'Followed Hyperlink.')
```
Followed Hyperlink.
#### INDEX_1
```
INDEX_1 = (-11, 'Index 1.')
```
Index 1.
#### INDEX_2
```
INDEX_2 = (-12, 'Index 2.')
```
Index 2.
#### INDEX_3
```
INDEX_3 = (-13, 'Index 3.')
```
Index 3.
#### INDEX_4
```
INDEX_4 = (-14, 'Index 4.')
```
Index 4.
#### INDEX_5
```
INDEX_5 = (-15, 'Index 5.')
```
Index 5.
#### INDEX_6
```
INDEX_6 = (-16, 'Index 6.')
```
Index 6.
#### INDEX_7
```
INDEX_7 = (-17, 'Index 7.')
```
Index 7.
#### INDEX_8
```
INDEX_8 = (-18, 'Index 8.')
```
Index 8.
#### INDEX_9
```
INDEX_9 = (-19, 'Index 9.')
```
Index 9.
#### INDEX_HEADING
```
INDEX_HEADING = (-34, 'Index Heading')
```
Index Heading
#### INTENSE_EMPHASIS
```
INTENSE_EMPHASIS = (-262, 'Intense Emphasis.')
```
Intense Emphasis.
#### INTENSE_QUOTE
```
INTENSE_QUOTE = (-182, 'Intense Quote.')
```
Intense Quote.
#### INTENSE_REFERENCE
```
INTENSE_REFERENCE = (-264, 'Intense Reference.')
```
Intense Reference.
#### LINE_NUMBER
```
LINE_NUMBER = (-41, 'Line Number.')
```
Line Number.
#### LIST
```
LIST = (-48, 'List.')
```
List.
#### LIST_2
```
LIST_2 = (-51, 'List 2.')
```
List 2.
#### LIST_3
```
LIST_3 = (-52, 'List 3.')
```
List 3.
#### LIST_4
```
LIST_4 = (-53, 'List 4.')
```
List 4.
#### LIST_5
```
LIST_5 = (-54, 'List 5.')
```
List 5.
#### LIST_BULLET
```
LIST_BULLET = (-49, 'List Bullet.')
```
List Bullet.
#### LIST_BULLET_2
```
LIST_BULLET_2 = (-55, 'List Bullet 2.')
```
List Bullet 2.
#### LIST_BULLET_3
```
LIST_BULLET_3 = (-56, 'List Bullet 3.')
```
List Bullet 3.
#### LIST_BULLET_4
```
LIST_BULLET_4 = (-57, 'List Bullet 4.')
```
List Bullet 4.
#### LIST_BULLET_5
```
LIST_BULLET_5 = (-58, 'List Bullet 5.')
```
List Bullet 5.
#### LIST_CONTINUE
```
LIST_CONTINUE = (-69, 'List Continue.')
```
List Continue.
#### LIST_CONTINUE_2
```
LIST_CONTINUE_2 = (-70, 'List Continue 2.')
```
List Continue 2.
#### LIST_CONTINUE_3
```
LIST_CONTINUE_3 = (-71, 'List Continue 3.')
```
List Continue 3.
#### LIST_CONTINUE_4
```
LIST_CONTINUE_4 = (-72, 'List Continue 4.')
```
List Continue 4.
#### LIST_CONTINUE_5
```
LIST_CONTINUE_5 = (-73, 'List Continue 5.')
```
List Continue 5.
#### LIST_NUMBER
```
LIST_NUMBER = (-50, 'List Number.')
```
List Number.
#### LIST_NUMBER_2
```
LIST_NUMBER_2 = (-59, 'List Number 2.')
```
List Number 2.
#### LIST_NUMBER_3
```
LIST_NUMBER_3 = (-60, 'List Number 3.')
```
List Number 3.
#### LIST_NUMBER_4
```
LIST_NUMBER_4 = (-61, 'List Number 4.')
```
List Number 4.
#### LIST_NUMBER_5
```
LIST_NUMBER_5 = (-62, 'List Number 5.')
```
List Number 5.
#### LIST_PARAGRAPH
```
LIST_PARAGRAPH = (-180, 'List Paragraph.')
```
List Paragraph.
#### MACRO_TEXT
```
MACRO_TEXT = (-46, 'Macro Text.')
```
Macro Text.
#### MESSAGE_HEADER
```
MESSAGE_HEADER = (-74, 'Message Header.')
```
Message Header.
#### NAV_PANE
```
NAV_PANE = (-90, 'Document Map.')
```
Document Map.
#### NORMAL
```
NORMAL = (-1, 'Normal.')
```
Normal.
#### NORMAL_INDENT
```
NORMAL_INDENT = (-29, 'Normal Indent.')
```
Normal Indent.
#### NORMAL_OBJECT
```
NORMAL_OBJECT = (-158, 'Normal (applied to an object).')
```
Normal (applied to an object).
#### NORMAL_TABLE
```
NORMAL_TABLE = (-106, 'Normal (applied within a table).')
```
Normal (applied within a table).
#### NOTE_HEADING
```
NOTE_HEADING = (-80, 'Note Heading.')
```
Note Heading.
#### PAGE_NUMBER
```
PAGE_NUMBER = (-42, 'Page Number.')
```
Page Number.
#### PLAIN_TEXT
```
PLAIN_TEXT = (-91, 'Plain Text.')
```
Plain Text.
#### QUOTE
```
QUOTE = (-181, 'Quote.')
```
Quote.
#### SALUTATION
```
SALUTATION = (-76, 'Salutation.')
```
Salutation.
#### SIGNATURE
```
SIGNATURE = (-65, 'Signature.')
```
Signature.
#### STRONG
```
STRONG = (-88, 'Strong.')
```
Strong.
#### SUBTITLE
```
SUBTITLE = (-75, 'Subtitle.')
```
Subtitle.
#### SUBTLE_EMPHASIS
```
SUBTLE_EMPHASIS = (-261, 'Subtle Emphasis.')
```
Subtle Emphasis.
#### SUBTLE_REFERENCE
```
SUBTLE_REFERENCE = (-263, 'Subtle Reference.')
```
Subtle Reference.
#### TABLE_COLORFUL_GRID
```
TABLE_COLORFUL_GRID = (-172, 'Colorful Grid.')
```
Colorful Grid.
#### TABLE_COLORFUL_LIST
```
TABLE_COLORFUL_LIST = (-171, 'Colorful List.')
```
Colorful List.
#### TABLE_COLORFUL_SHADING
```
TABLE_COLORFUL_SHADING = (-170, 'Colorful Shading.')
```
Colorful Shading.
#### TABLE_DARK_LIST
```
TABLE_DARK_LIST = (-169, 'Dark List.')
```
Dark List.
#### TABLE_LIGHT_GRID
```
TABLE_LIGHT_GRID = (-161, 'Light Grid.')
```
Light Grid.
#### TABLE_LIGHT_GRID_ACCENT_1
```
TABLE_LIGHT_GRID_ACCENT_1 = (-175, 'Light Grid Accent 1.')
```
Light Grid Accent 1.
#### TABLE_LIGHT_LIST
```
TABLE_LIGHT_LIST = (-160, 'Light List.')
```
Light List.
#### TABLE_LIGHT_LIST_ACCENT_1
```
TABLE_LIGHT_LIST_ACCENT_1 = (-174, 'Light List Accent 1.')
```
Light List Accent 1.
#### TABLE_LIGHT_SHADING
```
TABLE_LIGHT_SHADING = (-159, 'Light Shading.')
```
Light Shading.
#### TABLE_LIGHT_SHADING_ACCENT_1
```
TABLE_LIGHT_SHADING_ACCENT_1 = (
-173,
"Light Shading Accent 1.",
)
```
Light Shading Accent 1.
#### TABLE_MEDIUM_GRID_1
```
TABLE_MEDIUM_GRID_1 = (-166, 'Medium Grid 1.')
```
Medium Grid 1.
#### TABLE_MEDIUM_GRID_2
```
TABLE_MEDIUM_GRID_2 = (-167, 'Medium Grid 2.')
```
Medium Grid 2.
#### TABLE_MEDIUM_GRID_3
```
TABLE_MEDIUM_GRID_3 = (-168, 'Medium Grid 3.')
```
Medium Grid 3.
#### TABLE_MEDIUM_LIST_1
```
TABLE_MEDIUM_LIST_1 = (-164, 'Medium List 1.')
```
Medium List 1.
#### TABLE_MEDIUM_LIST_1_ACCENT_1
```
TABLE_MEDIUM_LIST_1_ACCENT_1 = (
-178,
"Medium List 1 Accent 1.",
)
```
Medium List 1 Accent 1.
#### TABLE_MEDIUM_LIST_2
```
TABLE_MEDIUM_LIST_2 = (-165, 'Medium List 2.')
```
Medium List 2.
#### TABLE_MEDIUM_SHADING_1
```
TABLE_MEDIUM_SHADING_1 = (-162, 'Medium Shading 1.')
```
Medium Shading 1.
#### TABLE_MEDIUM_SHADING_1_ACCENT_1
```
TABLE_MEDIUM_SHADING_1_ACCENT_1 = (
-176,
"Medium Shading 1 Accent 1.",
)
```
Medium Shading 1 Accent 1.
#### TABLE_MEDIUM_SHADING_2
```
TABLE_MEDIUM_SHADING_2 = (-163, 'Medium Shading 2.')
```
Medium Shading 2.
#### TABLE_MEDIUM_SHADING_2_ACCENT_1
```
TABLE_MEDIUM_SHADING_2_ACCENT_1 = (
-177,
"Medium Shading 2 Accent 1.",
)
```
Medium Shading 2 Accent 1.
#### TABLE_OF_AUTHORITIES
```
TABLE_OF_AUTHORITIES = (-45, 'Table of Authorities.')
```
Table of Authorities.
#### TABLE_OF_FIGURES
```
TABLE_OF_FIGURES = (-36, 'Table of Figures.')
```
Table of Figures.
#### TITLE
```
TITLE = (-63, 'Title.')
```
Title.
#### TOAHEADING
```
TOAHEADING = (-47, 'TOA Heading.')
```
TOA Heading.
#### TOC_1
```
TOC_1 = (-20, 'TOC 1.')
```
TOC 1.
#### TOC_2
```
TOC_2 = (-21, 'TOC 2.')
```
TOC 2.
#### TOC_3
```
TOC_3 = (-22, 'TOC 3.')
```
TOC 3.
#### TOC_4
```
TOC_4 = (-23, 'TOC 4.')
```
TOC 4.
#### TOC_5
```
TOC_5 = (-24, 'TOC 5.')
```
TOC 5.
#### TOC_6
```
TOC_6 = (-25, 'TOC 6.')
```
TOC 6.
#### TOC_7
```
TOC_7 = (-26, 'TOC 7.')
```
TOC 7.
#### TOC_8
```
TOC_8 = (-27, 'TOC 8.')
```
TOC 8.
#### TOC_9
```
TOC_9 = (-28, 'TOC 9.')
```
TOC 9.
### WD_STYLE_TYPE
Bases: `BaseXmlEnum`
Specifies one of the four style types: paragraph, character, list, or table.
Example:
```
from docx import Document
from docx.enum.style import WD_STYLE_TYPE
styles = Document().styles
assert styles[0].type == WD_STYLE_TYPE.PARAGRAPH
```
MS API name: `WdStyleType`
http://msdn.microsoft.com/en-us/library/office/ff196870.aspx
#### CHARACTER
```
CHARACTER = (2, 'character', 'Character style.')
```
Character style.
#### LIST
```
LIST = (4, 'numbering', 'List style.')
```
List style.
#### PARAGRAPH
```
PARAGRAPH = (1, 'paragraph', 'Paragraph style.')
```
Paragraph style.
#### TABLE
```
TABLE = (3, 'table', 'Table style.')
```
Table style.
## table
Enumerations related to tables in WordprocessingML files.
### WD_CELL_VERTICAL_ALIGNMENT
Bases: `BaseXmlEnum`
Alias: **WD_ALIGN_VERTICAL**
Specifies the vertical alignment of text in one or more cells of a table.
Example:
```
from docx.enum.table import WD_ALIGN_VERTICAL
table = document.add_table(3, 3)
table.cell(0, 0).vertical_alignment = WD_ALIGN_VERTICAL.BOTTOM
```
MS API name: `WdCellVerticalAlignment`
https://msdn.microsoft.com/en-us/library/office/ff193345.aspx
#### TOP
```
TOP = (
0,
"top",
"Text is aligned to the top border of the cell.",
)
```
Text is aligned to the top border of the cell.
#### CENTER
```
CENTER = (
1,
"center",
"Text is aligned to the center of the cell.",
)
```
Text is aligned to the center of the cell.
#### BOTTOM
```
BOTTOM = (
3,
"bottom",
"Text is aligned to the bottom border of the cell.",
)
```
Text is aligned to the bottom border of the cell.
#### BOTH
```
BOTH = (
101,
"both",
"This is an option in the OpenXml spec, but not in Word itself. It's not clear what Word behavior this setting produces. If you find out please let us know and we'll update this documentation. Otherwise, probably best to avoid this option.",
)
```
This is an option in the OpenXml spec, but not in Word itself.
It's not clear what Word behavior this setting produces. If you find out please let us know and we'll update this documentation. Otherwise, probably best to avoid this option.
### WD_ROW_HEIGHT_RULE
Bases: `BaseXmlEnum`
Alias: **WD_ROW_HEIGHT**
Specifies the rule for determining the height of a table row
Example:
```
from docx.enum.table import WD_ROW_HEIGHT_RULE
table = document.add_table(3, 3)
table.rows[0].height_rule = WD_ROW_HEIGHT_RULE.EXACTLY
```
MS API name: `WdRowHeightRule`
https://msdn.microsoft.com/en-us/library/office/ff193620.aspx
#### AUTO
```
AUTO = (
0,
"auto",
"The row height is adjusted to accommodate the tallest value in the row.",
)
```
The row height is adjusted to accommodate the tallest value in the row.
#### AT_LEAST
```
AT_LEAST = (
1,
"atLeast",
"The row height is at least a minimum specified value.",
)
```
The row height is at least a minimum specified value.
#### EXACTLY
```
EXACTLY = (2, 'exact', 'The row height is an exact value.')
```
The row height is an exact value.
### WD_LINE_STYLE
Bases: `BaseXmlEnum`
Specifies the line style of a table or cell border.
Example:
```
from docx.enum.table import WD_LINE_STYLE
from docx.shared import Pt
table = document.add_table(3, 3)
table.borders["top"].line = WD_LINE_STYLE.SINGLE
table.borders["top"].size = Pt(1)
```
Only the structural line styles are members here. The `ST_Border` schema type also admits some 170 decorative "page border art" values such as `"apples"` and `"zigZagStitch"`; those are meaningful only on a page border, and Word does not offer or write them for a table or cell border.
MS API name: `WdLineStyle`
https://learn.microsoft.com/en-us/office/vba/api/word.wdlinestyle
#### NONE
```
NONE = (0, 'none', 'No border.')
```
No border.
#### SINGLE
```
SINGLE = (1, 'single', 'A single solid line.')
```
A single solid line.
#### DOT
```
DOT = (2, 'dotted', 'A dotted line.')
```
A dotted line.
#### DASH_SMALL_GAP
```
DASH_SMALL_GAP = (
3,
"dashSmallGap",
"A dashed line with small gaps.",
)
```
A dashed line with small gaps.
#### DASH_LARGE_GAP
```
DASH_LARGE_GAP = (
4,
"dashed",
"A dashed line with large gaps.",
)
```
A dashed line with large gaps.
#### DASH_DOT
```
DASH_DOT = (
5,
"dotDash",
"A line of alternating dashes and dots.",
)
```
A line of alternating dashes and dots.
#### DASH_DOT_DOT
```
DASH_DOT_DOT = (
6,
"dotDotDash",
"A line of dashes each followed by two dots.",
)
```
A line of dashes each followed by two dots.
#### DOUBLE
```
DOUBLE = (7, 'double', 'Two parallel solid lines.')
```
Two parallel solid lines.
#### TRIPLE
```
TRIPLE = (8, 'triple', 'Three parallel solid lines.')
```
Three parallel solid lines.
#### THIN_THICK_SMALL_GAP
```
THIN_THICK_SMALL_GAP = (
9,
"thinThickSmallGap",
"A thin line and a thick line separated by a small gap.",
)
```
A thin line and a thick line separated by a small gap.
#### THICK_THIN_SMALL_GAP
```
THICK_THIN_SMALL_GAP = (
10,
"thickThinSmallGap",
"A thick line and a thin line separated by a small gap.",
)
```
A thick line and a thin line separated by a small gap.
#### THIN_THICK_THIN_SMALL_GAP
```
THIN_THICK_THIN_SMALL_GAP = (
11,
"thinThickThinSmallGap",
"A thin, a thick and a thin line separated by small gaps.",
)
```
A thin, a thick and a thin line separated by small gaps.
#### THIN_THICK_MED_GAP
```
THIN_THICK_MED_GAP = (
12,
"thinThickMediumGap",
"A thin line and a thick line separated by a medium gap.",
)
```
A thin line and a thick line separated by a medium gap.
#### THICK_THIN_MED_GAP
```
THICK_THIN_MED_GAP = (
13,
"thickThinMediumGap",
"A thick line and a thin line separated by a medium gap.",
)
```
A thick line and a thin line separated by a medium gap.
#### THIN_THICK_THIN_MED_GAP
```
THIN_THICK_THIN_MED_GAP = (
14,
"thinThickThinMediumGap",
"A thin, a thick and a thin line separated by medium gaps.",
)
```
A thin, a thick and a thin line separated by medium gaps.
#### THIN_THICK_LARGE_GAP
```
THIN_THICK_LARGE_GAP = (
15,
"thinThickLargeGap",
"A thin line and a thick line separated by a large gap.",
)
```
A thin line and a thick line separated by a large gap.
#### THICK_THIN_LARGE_GAP
```
THICK_THIN_LARGE_GAP = (
16,
"thickThinLargeGap",
"A thick line and a thin line separated by a large gap.",
)
```
A thick line and a thin line separated by a large gap.
#### THIN_THICK_THIN_LARGE_GAP
```
THIN_THICK_THIN_LARGE_GAP = (
17,
"thinThickThinLargeGap",
"A thin, a thick and a thin line separated by large gaps.",
)
```
A thin, a thick and a thin line separated by large gaps.
#### SINGLE_WAVY
```
SINGLE_WAVY = (18, 'wave', 'A single wavy line.')
```
A single wavy line.
#### DOUBLE_WAVY
```
DOUBLE_WAVY = (19, "doubleWave", "Two parallel wavy lines.")
```
Two parallel wavy lines.
#### DASH_DOT_STROKED
```
DASH_DOT_STROKED = (
20,
"dashDotStroked",
"A line of slanting dashes and dots.",
)
```
A line of slanting dashes and dots.
#### EMBOSS_3D
```
EMBOSS_3D = (
21,
"threeDEmboss",
"A line that appears embossed.",
)
```
A line that appears embossed.
#### ENGRAVE_3D
```
ENGRAVE_3D = (
22,
"threeDEngrave",
"A line that appears engraved.",
)
```
A line that appears engraved.
#### OUTSET
```
OUTSET = (
23,
"outset",
"A line that makes the enclosed area appear raised.",
)
```
A line that makes the enclosed area appear raised.
#### INSET
```
INSET = (
24,
"inset",
"A line that makes the enclosed area appear sunken.",
)
```
A line that makes the enclosed area appear sunken.
#### THICK
```
THICK = (
100,
"thick",
"A single thick solid line. This is an OpenXml value with no `WdLineStyle` counterpart; Word renders it as a heavier `SINGLE`.",
)
```
A single thick solid line.
This is an OpenXml value with no `WdLineStyle` counterpart; Word renders it as a heavier `SINGLE`.
#### NIL
```
NIL = (
101,
"nil",
"No border, and no space reserved for one. Distinct from `NONE` only in that `NONE` is the value Word writes when a border is explicitly turned off, whereas `nil` also appears as the default in a table style.",
)
```
No border, and no space reserved for one.
Distinct from `NONE` only in that `NONE` is the value Word writes when a border is explicitly turned off, whereas `nil` also appears as the default in a table style.
### WD_TABLE_ALIGNMENT
Bases: `BaseXmlEnum`
Specifies table justification type.
Example:
```
from docx.enum.table import WD_TABLE_ALIGNMENT
table = document.add_table(3, 3)
table.alignment = WD_TABLE_ALIGNMENT.CENTER
```
MS API name: `WdRowAlignment`
http://office.microsoft.com/en-us/word-help/HV080607259.aspx
#### LEFT
```
LEFT = (0, 'left', 'Left-aligned')
```
Left-aligned
#### CENTER
```
CENTER = (1, 'center', 'Center-aligned.')
```
Center-aligned.
#### RIGHT
```
RIGHT = (2, 'right', 'Right-aligned.')
```
Right-aligned.
### WD_TABLE_DIRECTION
Bases: `BaseEnum`
Specifies the direction in which an application orders cells in the specified table or row.
Example:
```
from docx.enum.table import WD_TABLE_DIRECTION
table = document.add_table(3, 3)
table.direction = WD_TABLE_DIRECTION.RTL
```
MS API name: `WdTableDirection`
http://msdn.microsoft.com/en-us/library/ff835141.aspx
#### LTR
```
LTR = (
0,
"The table or row is arranged with the first column in the leftmost position.",
)
```
The table or row is arranged with the first column in the leftmost position.
#### RTL
```
RTL = (
1,
"The table or row is arranged with the first column in the rightmost position.",
)
```
The table or row is arranged with the first column in the rightmost position.
## text
Enumerations related to text in WordprocessingML files.
### WD_PARAGRAPH_ALIGNMENT
Bases: `BaseXmlEnum`
Alias: **WD_ALIGN_PARAGRAPH**
Specifies paragraph justification type.
Example:
```
from docx.enum.text import WD_ALIGN_PARAGRAPH
paragraph = document.add_paragraph()
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
```
#### LEFT
```
LEFT = (0, 'left', 'Left-aligned')
```
Left-aligned
#### CENTER
```
CENTER = (1, 'center', 'Center-aligned.')
```
Center-aligned.
#### RIGHT
```
RIGHT = (2, 'right', 'Right-aligned.')
```
Right-aligned.
#### JUSTIFY
```
JUSTIFY = (3, 'both', 'Fully justified.')
```
Fully justified.
#### DISTRIBUTE
```
DISTRIBUTE = (
4,
"distribute",
"Paragraph characters are distributed to fill entire width of paragraph.",
)
```
Paragraph characters are distributed to fill entire width of paragraph.
#### JUSTIFY_MED
```
JUSTIFY_MED = (
5,
"mediumKashida",
"Justified with a medium character compression ratio.",
)
```
Justified with a medium character compression ratio.
#### JUSTIFY_HI
```
JUSTIFY_HI = (
7,
"highKashida",
"Justified with a high character compression ratio.",
)
```
Justified with a high character compression ratio.
#### JUSTIFY_LOW
```
JUSTIFY_LOW = (
8,
"lowKashida",
"Justified with a low character compression ratio.",
)
```
Justified with a low character compression ratio.
#### THAI_JUSTIFY
```
THAI_JUSTIFY = (
9,
"thaiDistribute",
"Justified according to Thai formatting layout.",
)
```
Justified according to Thai formatting layout.
### WD_BREAK_TYPE
Bases: `Enum`
Corresponds to WdBreakType enumeration.
http://msdn.microsoft.com/en-us/library/office/ff195905.aspx.
### WD_COLOR_INDEX
Bases: `BaseXmlEnum`
Specifies a standard preset color to apply.
Used for font highlighting and perhaps other applications.
- MS API name: `WdColorIndex`
- URL: https://msdn.microsoft.com/EN-US/library/office/ff195343.aspx
#### INHERITED
```
INHERITED = (
-1,
None,
"Color is inherited from the style hierarchy.",
)
```
Color is inherited from the style hierarchy.
#### NO_HIGHLIGHT
```
NO_HIGHLIGHT = (-2, 'none', 'Explicitly not highlighted.')
```
Explicitly not highlighted.
Distinct from `None`, which means no `w:highlight` element is present and the highlight is therefore inherited. `w:highlight` with `w:val="none"` overrides an inherited highlight and is written by Word when highlighting is cleared on a run that sits under a style supplying one.
The MS API assigns `wdNoHighlight` the value 0, the same value as `wdAuto`. Two members cannot share a value here, so this member takes a distinct negative value in the manner of `INHERITED`.
#### AUTO
```
AUTO = (
0,
"default",
"Automatic color. Default; usually black.",
)
```
Automatic color. Default; usually black.
#### BLACK
```
BLACK = (1, 'black', 'Black color.')
```
Black color.
#### BLUE
```
BLUE = (2, 'blue', 'Blue color')
```
Blue color
#### BRIGHT_GREEN
```
BRIGHT_GREEN = (4, 'green', 'Bright green color.')
```
Bright green color.
#### DARK_BLUE
```
DARK_BLUE = (9, 'darkBlue', 'Dark blue color.')
```
Dark blue color.
#### DARK_RED
```
DARK_RED = (13, 'darkRed', 'Dark red color.')
```
Dark red color.
#### DARK_YELLOW
```
DARK_YELLOW = (14, 'darkYellow', 'Dark yellow color.')
```
Dark yellow color.
#### GRAY_25
```
GRAY_25 = (16, 'lightGray', '25% shade of gray color.')
```
25% shade of gray color.
#### GRAY_50
```
GRAY_50 = (15, 'darkGray', '50% shade of gray color.')
```
50% shade of gray color.
#### GREEN
```
GREEN = (11, 'darkGreen', 'Green color.')
```
Green color.
#### PINK
```
PINK = (5, 'magenta', 'Pink color.')
```
Pink color.
#### RED
```
RED = (6, 'red', 'Red color.')
```
Red color.
#### TEAL
```
TEAL = (10, 'darkCyan', 'Teal color.')
```
Teal color.
#### TURQUOISE
```
TURQUOISE = (3, 'cyan', 'Turquoise color.')
```
Turquoise color.
#### VIOLET
```
VIOLET = (12, 'darkMagenta', 'Violet color.')
```
Violet color.
#### WHITE
```
WHITE = (8, 'white', 'White color.')
```
White color.
#### YELLOW
```
YELLOW = (7, 'yellow', 'Yellow color.')
```
Yellow color.
### WD_CONTENT_CONTROL_TYPE
Bases: `BaseEnum`
Specifies the kind of a structured document tag (`w:sdt`), aka content control.
The kind is determined by which child of `w:sdtPr` is present, not by an attribute value, so these members have no XML value mapping.
- MS API name: `WdContentControlType`
- URL: https://learn.microsoft.com/en-us/office/vba/api/word.wdcontentcontroltype
#### RICH_TEXT
```
RICH_TEXT = (
0,
"Formatted text, which may contain multiple paragraphs.",
)
```
Formatted text, which may contain multiple paragraphs.
#### TEXT
```
TEXT = (
1,
"Plain text, a single run without formatting of its own.",
)
```
Plain text, a single run without formatting of its own.
#### PICTURE
```
PICTURE = (2, 'A single picture.')
```
A single picture.
#### COMBO_BOX
```
COMBO_BOX = (
3,
"A list of choices that also accepts typed text.",
)
```
A list of choices that also accepts typed text.
#### DROPDOWN_LIST
```
DROPDOWN_LIST = (
4,
"A list of choices, one of which must be selected.",
)
```
A list of choices, one of which must be selected.
#### BUILDING_BLOCK_GALLERY
```
BUILDING_BLOCK_GALLERY = (
5,
"A gallery of building blocks, e.g. a cover page.",
)
```
A gallery of building blocks, e.g. a cover page.
#### DATE
```
DATE = (6, 'A date, entered through a calendar picker.')
```
A date, entered through a calendar picker.
#### GROUP
```
GROUP = (
7,
"A grouping of content that is edited as a unit.",
)
```
A grouping of content that is edited as a unit.
#### CHECKBOX
```
CHECKBOX = (8, 'A check box, checked or unchecked.')
```
A check box, checked or unchecked.
Written by Word as the `w14:checkbox` extension element, not as part of the ISO schema.
#### REPEATING_SECTION
```
REPEATING_SECTION = (
9,
"A section repeated once per item in a bound collection.",
)
```
A section repeated once per item in a bound collection.
### WD_FONT_HINT
Bases: `BaseXmlEnum`
Specifies which `w:rFonts` typeface slot Word prefers for ambiguous characters.
A character that belongs to no particular script — a space, a digit, punctuation — could be rendered from more than one slot, and the hint settles it. Getting this wrong is a common cause of East Asian text rendering in the wrong typeface.
There is no MS API enumeration for this; it corresponds to the `ST_Hint` schema type and the member values are this library's own.
#### DEFAULT
```
DEFAULT = (
0,
"default",
"Use the ASCII typeface for ambiguous characters.",
)
```
Use the ASCII typeface for ambiguous characters.
#### EAST_ASIA
```
EAST_ASIA = (
1,
"eastAsia",
"Use the East Asian typeface for ambiguous characters.",
)
```
Use the East Asian typeface for ambiguous characters.
#### COMPLEX_SCRIPT
```
COMPLEX_SCRIPT = (
2,
"cs",
"Use the complex-script typeface for ambiguous characters.",
)
```
Use the complex-script typeface for ambiguous characters.
### WD_FORM_FIELD_TYPE
Bases: `BaseEnum`
Specifies the kind of a legacy form field.
The kind is determined by which child of `w:ffData` is present, not by an attribute value, so these members have no XML value mapping.
- MS API name: `WdFieldType` (the form-field subset)
- URL: https://learn.microsoft.com/en-us/office/vba/api/word.wdfieldtype
#### TEXT
```
TEXT = (70, 'A text input, which Word calls FORMTEXT.')
```
A text input, which Word calls FORMTEXT.
#### CHECK_BOX
```
CHECK_BOX = (
71,
"A check box, which Word calls FORMCHECKBOX.",
)
```
A check box, which Word calls FORMCHECKBOX.
#### DROP_DOWN
```
DROP_DOWN = (
83,
"A drop-down list, which Word calls FORMDROPDOWN.",
)
```
A drop-down list, which Word calls FORMDROPDOWN.
### WD_LINE_SPACING
Bases: `BaseXmlEnum`
Specifies a line spacing format to be applied to a paragraph.
Example:
```
from docx.enum.text import WD_LINE_SPACING
paragraph = document.add_paragraph()
paragraph.line_spacing_rule = WD_LINE_SPACING.EXACTLY
```
MS API name: `WdLineSpacing`
URL: http://msdn.microsoft.com/en-us/library/office/ff844910.aspx
#### SINGLE
```
SINGLE = (0, 'UNMAPPED', 'Single spaced (default).')
```
Single spaced (default).
#### ONE_POINT_FIVE
```
ONE_POINT_FIVE = (
1,
"UNMAPPED",
"Space-and-a-half line spacing.",
)
```
Space-and-a-half line spacing.
#### DOUBLE
```
DOUBLE = (2, 'UNMAPPED', 'Double spaced.')
```
Double spaced.
#### AT_LEAST
```
AT_LEAST = (
3,
"atLeast",
"Minimum line spacing is specified amount. Amount is specified separately.",
)
```
Minimum line spacing is specified amount. Amount is specified separately.
#### EXACTLY
```
EXACTLY = (
4,
"exact",
"Line spacing is exactly specified amount. Amount is specified separately.",
)
```
Line spacing is exactly specified amount. Amount is specified separately.
#### MULTIPLE
```
MULTIPLE = (
5,
"auto",
"Line spacing is specified as multiple of line heights. Changing font size will change line spacing proportionately.",
)
```
Line spacing is specified as multiple of line heights. Changing font size will change the line spacing proportionately.
### WD_SHADING_PATTERN
Bases: `BaseXmlEnum`
Specifies the pattern drawn over the background of shaded content.
The pattern is drawn in the shading *color* over the shading *fill*. The common case is CLEAR, which draws no pattern and leaves the fill as a solid background.
- ISO/IEC 29500-1 §17.18.78 (`ST_Shd`)
#### NIL
```
NIL = (
0,
"nil",
"No shading. Equivalent to no `w:shd` element at all.",
)
```
No shading. Equivalent to no `w:shd` element at all.
#### CLEAR
```
CLEAR = (
1,
"clear",
"No pattern; the fill color forms a solid background.",
)
```
No pattern; the fill color forms a solid background.
This is what Word writes for an ordinary background color, and what this library writes when shading is applied without naming a pattern.
#### SOLID
```
SOLID = (
2,
"solid",
"The pattern color entirely covers the fill color.",
)
```
The pattern color entirely covers the fill color.
Note the reversal: with SOLID the visible background is the shading *color*, not the fill.
#### HORZ_STRIPE
```
HORZ_STRIPE = (3, 'horzStripe', 'Horizontal stripes.')
```
Horizontal stripes.
#### VERT_STRIPE
```
VERT_STRIPE = (4, 'vertStripe', 'Vertical stripes.')
```
Vertical stripes.
#### REVERSE_DIAG_STRIPE
```
REVERSE_DIAG_STRIPE = (
5,
"reverseDiagStripe",
"Diagonal stripes, upward to right.",
)
```
Diagonal stripes running upward to the right.
#### DIAG_STRIPE
```
DIAG_STRIPE = (
6,
"diagStripe",
"Diagonal stripes, downward to right.",
)
```
Diagonal stripes running downward to the right.
#### HORZ_CROSS
```
HORZ_CROSS = (
7,
"horzCross",
"A horizontal and vertical crosshatch.",
)
```
A horizontal and vertical crosshatch.
#### DIAG_CROSS
```
DIAG_CROSS = (8, 'diagCross', 'A diagonal crosshatch.')
```
A diagonal crosshatch.
#### THIN_HORZ_STRIPE
```
THIN_HORZ_STRIPE = (
9,
"thinHorzStripe",
"Narrow horizontal stripes.",
)
```
Narrow horizontal stripes.
#### THIN_VERT_STRIPE
```
THIN_VERT_STRIPE = (
10,
"thinVertStripe",
"Narrow vertical stripes.",
)
```
Narrow vertical stripes.
#### THIN_REVERSE_DIAG_STRIPE
```
THIN_REVERSE_DIAG_STRIPE = (
11,
"thinReverseDiagStripe",
"Narrow diagonal stripes, upward to right.",
)
```
Narrow diagonal stripes running upward to the right.
#### THIN_DIAG_STRIPE
```
THIN_DIAG_STRIPE = (
12,
"thinDiagStripe",
"Narrow diagonal stripes, downward to right.",
)
```
Narrow diagonal stripes running downward to the right.
#### THIN_HORZ_CROSS
```
THIN_HORZ_CROSS = (
13,
"thinHorzCross",
"A narrow horizontal and vertical crosshatch.",
)
```
A narrow horizontal and vertical crosshatch.
#### THIN_DIAG_CROSS
```
THIN_DIAG_CROSS = (
14,
"thinDiagCross",
"A narrow diagonal crosshatch.",
)
```
A narrow diagonal crosshatch.
#### PCT_5
```
PCT_5 = (
15,
"pct5",
"5% of the pattern color over the fill color.",
)
```
5% of the pattern color over the fill color.
#### PCT_10
```
PCT_10 = (
16,
"pct10",
"10% of the pattern color over the fill color.",
)
```
10% of the pattern color over the fill color.
#### PCT_12
```
PCT_12 = (
17,
"pct12",
"12.5% of the pattern color over the fill color.",
)
```
12.5% of the pattern color over the fill color.
#### PCT_15
```
PCT_15 = (
18,
"pct15",
"15% of the pattern color over the fill color.",
)
```
15% of the pattern color over the fill color.
#### PCT_20
```
PCT_20 = (
19,
"pct20",
"20% of the pattern color over the fill color.",
)
```
20% of the pattern color over the fill color.
#### PCT_25
```
PCT_25 = (
20,
"pct25",
"25% of the pattern color over the fill color.",
)
```
25% of the pattern color over the fill color.
#### PCT_30
```
PCT_30 = (
21,
"pct30",
"30% of the pattern color over the fill color.",
)
```
30% of the pattern color over the fill color.
#### PCT_35
```
PCT_35 = (
22,
"pct35",
"35% of the pattern color over the fill color.",
)
```
35% of the pattern color over the fill color.
#### PCT_37
```
PCT_37 = (
23,
"pct37",
"37.5% of the pattern color over the fill color.",
)
```
37.5% of the pattern color over the fill color.
#### PCT_40
```
PCT_40 = (
24,
"pct40",
"40% of the pattern color over the fill color.",
)
```
40% of the pattern color over the fill color.
#### PCT_45
```
PCT_45 = (
25,
"pct45",
"45% of the pattern color over the fill color.",
)
```
45% of the pattern color over the fill color.
#### PCT_50
```
PCT_50 = (
26,
"pct50",
"50% of the pattern color over the fill color.",
)
```
50% of the pattern color over the fill color.
#### PCT_55
```
PCT_55 = (
27,
"pct55",
"55% of the pattern color over the fill color.",
)
```
55% of the pattern color over the fill color.
#### PCT_60
```
PCT_60 = (
28,
"pct60",
"60% of the pattern color over the fill color.",
)
```
60% of the pattern color over the fill color.
#### PCT_62
```
PCT_62 = (
29,
"pct62",
"62.5% of the pattern color over the fill color.",
)
```
62.5% of the pattern color over the fill color.
#### PCT_65
```
PCT_65 = (
30,
"pct65",
"65% of the pattern color over the fill color.",
)
```
65% of the pattern color over the fill color.
#### PCT_70
```
PCT_70 = (
31,
"pct70",
"70% of the pattern color over the fill color.",
)
```
70% of the pattern color over the fill color.
#### PCT_75
```
PCT_75 = (
32,
"pct75",
"75% of the pattern color over the fill color.",
)
```
75% of the pattern color over the fill color.
#### PCT_80
```
PCT_80 = (
33,
"pct80",
"80% of the pattern color over the fill color.",
)
```
80% of the pattern color over the fill color.
#### PCT_85
```
PCT_85 = (
34,
"pct85",
"85% of the pattern color over the fill color.",
)
```
85% of the pattern color over the fill color.
#### PCT_87
```
PCT_87 = (
35,
"pct87",
"87.5% of the pattern color over the fill color.",
)
```
87.5% of the pattern color over the fill color.
#### PCT_90
```
PCT_90 = (
36,
"pct90",
"90% of the pattern color over the fill color.",
)
```
90% of the pattern color over the fill color.
#### PCT_95
```
PCT_95 = (
37,
"pct95",
"95% of the pattern color over the fill color.",
)
```
95% of the pattern color over the fill color.
### WD_TAB_ALIGNMENT
Bases: `BaseXmlEnum`
Specifies the tab stop alignment to apply.
MS API name: `WdTabAlignment`
URL: https://msdn.microsoft.com/EN-US/library/office/ff195609.aspx
#### LEFT
```
LEFT = (0, 'left', 'Left-aligned.')
```
Left-aligned.
#### CENTER
```
CENTER = (1, 'center', 'Center-aligned.')
```
Center-aligned.
#### RIGHT
```
RIGHT = (2, 'right', 'Right-aligned.')
```
Right-aligned.
#### DECIMAL
```
DECIMAL = (3, 'decimal', 'Decimal-aligned.')
```
Decimal-aligned.
#### BAR
```
BAR = (4, 'bar', 'Bar-aligned.')
```
Bar-aligned.
#### LIST
```
LIST = (6, 'list', 'List-aligned. (deprecated)')
```
List-aligned. (deprecated)
#### CLEAR
```
CLEAR = (101, 'clear', 'Clear an inherited tab stop.')
```
Clear an inherited tab stop.
#### END
```
END = (102, 'end', 'Right-aligned. (deprecated)')
```
Right-aligned. (deprecated)
#### NUM
```
NUM = (103, 'num', 'Left-aligned. (deprecated)')
```
Left-aligned. (deprecated)
#### START
```
START = (104, 'start', 'Left-aligned. (deprecated)')
```
Left-aligned. (deprecated)
### WD_TAB_LEADER
Bases: `BaseXmlEnum`
Specifies the character to use as the leader with formatted tabs.
MS API name: `WdTabLeader`
URL: https://msdn.microsoft.com/en-us/library/office/ff845050.aspx
#### SPACES
```
SPACES = (0, 'none', 'Spaces. Default.')
```
Spaces. Default.
#### DOTS
```
DOTS = (1, 'dot', 'Dots.')
```
Dots.
#### DASHES
```
DASHES = (2, 'hyphen', 'Dashes.')
```
Dashes.
#### LINES
```
LINES = (3, 'underscore', 'Double lines.')
```
Double lines.
#### HEAVY
```
HEAVY = (4, 'heavy', 'A heavy line.')
```
A heavy line.
#### MIDDLE_DOT
```
MIDDLE_DOT = (5, 'middleDot', 'A vertically-centered dot.')
```
A vertically-centered dot.
### WD_TEXT_DIRECTION
Bases: `BaseXmlEnum`
Specifies the flow direction of text within a paragraph, section or table cell.
This is the writing direction — which way the lines run and whether the glyphs are rotated — and is a different thing from `bidi`, which is the base *reading* direction of a right-to-left paragraph.
Example:
```
from docx.enum.text import WD_TEXT_DIRECTION
cell.text_direction = WD_TEXT_DIRECTION.BT_LR # rotated header cell
```
The names spell out the two axes in the order Word writes them: `LR_TB` is left-to-right within a line, top-to-bottom between lines, which is ordinary horizontal Western layout.
#### LR_TB
```
LR_TB = (
0,
"lrTb",
"Horizontal, left to right. Ordinary Western layout.",
)
```
Horizontal, left to right. Ordinary Western layout.
#### TB_RL
```
TB_RL = (
1,
"tbRl",
"Vertical, right to left. Ordinary East Asian vertical layout.",
)
```
Vertical, right to left. Ordinary East Asian vertical layout.
#### BT_LR
```
BT_LR = (
2,
"btLr",
"Rotated 90 degrees counter-clockwise. A rotated table header.",
)
```
Rotated 90 degrees counter-clockwise. A rotated table header.
#### LR_TB_V
```
LR_TB_V = (
3,
"lrTbV",
"Horizontal, with each glyph rotated 90 degrees clockwise.",
)
```
Horizontal, with each glyph rotated 90 degrees clockwise.
#### TB_RL_V
```
TB_RL_V = (
4,
"tbRlV",
"Vertical, with each glyph rotated 90 degrees clockwise.",
)
```
Vertical, with each glyph rotated 90 degrees clockwise.
#### TB_LR_V
```
TB_LR_V = (
5,
"tbLrV",
"Vertical, left to right, with glyphs rotated.",
)
```
Vertical, left to right, with glyphs rotated.
### WD_TEXT_FORM_FIELD_TYPE
Bases: `BaseXmlEnum`
Specifies what a text form field accepts.
Example:
```
from docx.enum.text import WD_TEXT_FORM_FIELD_TYPE
form_field.text_type = WD_TEXT_FORM_FIELD_TYPE.NUMBER_TEXT
```
- MS API name: `WdTextFormFieldType`
- URL: https://learn.microsoft.com/en-us/office/vba/api/word.wdtextformfieldtype
#### REGULAR_TEXT
```
REGULAR_TEXT = (0, 'regular', 'Any text.')
```
Any text.
#### NUMBER_TEXT
```
NUMBER_TEXT = (1, 'number', 'A number.')
```
A number.
#### DATE_TEXT
```
DATE_TEXT = (2, 'date', 'A date.')
```
A date.
#### CURRENT_DATE_TEXT
```
CURRENT_DATE_TEXT = (
3,
"currentDate",
"The current date, filled in by Word.",
)
```
The current date, filled in by Word.
#### CURRENT_TIME_TEXT
```
CURRENT_TIME_TEXT = (
4,
"currentTime",
"The current time, filled in by Word.",
)
```
The current time, filled in by Word.
#### CALCULATION_TEXT
```
CALCULATION_TEXT = (
5,
"calculated",
"The result of an expression, computed by Word.",
)
```
The result of an expression, computed by Word.
### WD_UNDERLINE
Bases: `BaseXmlEnum`
Specifies the style of underline applied to a run of characters.
MS API name: `WdUnderline`
URL: http://msdn.microsoft.com/en-us/library/office/ff822388.aspx
#### INHERITED
```
INHERITED = (
-1,
None,
"Inherit underline setting from containing paragraph.",
)
```
Inherit underline setting from containing paragraph.
#### NONE
```
NONE = (
0,
"none",
"No underline.\n\nThis setting overrides any inherited underline value, so can be used to remove underline from a run that inherits underlining from its containing paragraph. Note this is not the same as assigning |None| to Run.underline. |None| is a valid assignment value, but causes the run to inherit its underline value. Assigning `WD_UNDERLINE.NONE` causes underlining to be unconditionally turned off.",
)
```
No underline.
This setting overrides any inherited underline value, so can be used to remove underline from a run that inherits underlining from its containing paragraph. Note this is not the same as assigning `None` to Run.underline. `None` is a valid assignment value, but causes the run to inherit its underline value. Assigning `WD_UNDERLINE.NONE` causes underlining to be unconditionally turned off.
#### SINGLE
```
SINGLE = (
1,
"single",
"A single line.\n\nNote that this setting is write-only in the sense that |True| (rather than `WD_UNDERLINE.SINGLE`) is returned for a run having this setting.",
)
```
A single line.
Note that this setting is write-only in the sense that `True` (rather than `WD_UNDERLINE.SINGLE`) is returned for a run having this setting.
#### WORDS
```
WORDS = (2, 'words', 'Underline individual words only.')
```
Underline individual words only.
#### DOUBLE
```
DOUBLE = (3, 'double', 'A double line.')
```
A double line.
#### DOTTED
```
DOTTED = (4, 'dotted', 'Dots.')
```
Dots.
#### THICK
```
THICK = (6, 'thick', 'A single thick line.')
```
A single thick line.
#### DASH
```
DASH = (7, 'dash', 'Dashes.')
```
Dashes.
#### DOT_DASH
```
DOT_DASH = (9, 'dotDash', 'Alternating dots and dashes.')
```
Alternating dots and dashes.
#### DOT_DOT_DASH
```
DOT_DOT_DASH = (
10,
"dotDotDash",
"An alternating dot-dot-dash pattern.",
)
```
An alternating dot-dot-dash pattern.
#### WAVY
```
WAVY = (11, 'wave', 'A single wavy line.')
```
A single wavy line.
#### DOTTED_HEAVY
```
DOTTED_HEAVY = (20, 'dottedHeavy', 'Heavy dots.')
```
Heavy dots.
#### DASH_HEAVY
```
DASH_HEAVY = (23, 'dashedHeavy', 'Heavy dashes.')
```
Heavy dashes.
#### DOT_DASH_HEAVY
```
DOT_DASH_HEAVY = (
25,
"dashDotHeavy",
"Alternating heavy dots and heavy dashes.",
)
```
Alternating heavy dots and heavy dashes.
#### DOT_DOT_DASH_HEAVY
```
DOT_DOT_DASH_HEAVY = (
26,
"dashDotDotHeavy",
"An alternating heavy dot-dot-dash pattern.",
)
```
An alternating heavy dot-dot-dash pattern.
#### WAVY_HEAVY
```
WAVY_HEAVY = (27, 'wavyHeavy', 'A heavy wavy line.')
```
A heavy wavy line.
#### DASH_LONG
```
DASH_LONG = (39, 'dashLong', 'Long dashes.')
```
Long dashes.
#### WAVY_DOUBLE
```
WAVY_DOUBLE = (43, 'wavyDouble', 'A double wavy line.')
```
A double wavy line.
#### DASH_LONG_HEAVY
```
DASH_LONG_HEAVY = (
55,
"dashLongHeavy",
"Long heavy dashes.",
)
```
Long heavy dashes.
## image
Provides objects that can characterize image streams.
That characterization is as to content type and size, as a required step in including them in a document.
## bmp
### Bmp
```
Bmp(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `BaseImageHeader`
Image header parser for BMP images.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### content_type
```
content_type
```
MIME content type for this image, unconditionally `image/bmp` for BMP images.
#### default_ext
```
default_ext
```
Default filename extension, always 'bmp' for BMP images.
#### from_stream
```
from_stream(stream)
```
Return Bmp instance having header properties parsed from the BMP image in `stream`.
Source code in `src/docx/image/bmp.py`
```
@classmethod
def from_stream(cls, stream):
"""Return |Bmp| instance having header properties parsed from the BMP image in
`stream`."""
stream_rdr = StreamReader(stream, LITTLE_ENDIAN)
px_width = stream_rdr.read_long(0x12)
px_height = stream_rdr.read_long(0x16)
horz_px_per_meter = stream_rdr.read_long(0x26)
vert_px_per_meter = stream_rdr.read_long(0x2A)
horz_dpi = cls._dpi(horz_px_per_meter)
vert_dpi = cls._dpi(vert_px_per_meter)
return cls(px_width, px_height, horz_dpi, vert_dpi)
```
## constants
Constants specific the the image sub-package.
### JPEG_MARKER_CODE
JPEG marker codes.
### MIME_TYPE
Image content types.
### PNG_CHUNK_TYPE
PNG chunk type names.
### TIFF_FLD_TYPE
Tag codes for TIFF Image File Directory (IFD) entries.
### TIFF_TAG
Tag codes for TIFF Image File Directory (IFD) entries.
## emf
Image header parser for EMF (Enhanced Metafile) images.
An EMF file opens with an `EMR_HEADER` record whose `ENHMETAHEADER` structure carries both the physical extent of the picture and the resolution of the device it was recorded against. Both are needed: the extent alone says how large the picture is, and the device resolution is what turns that into a pixel count that agrees with what Word shows.
### Emf
```
Emf(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `BaseImageHeader`
Image header parser for EMF images.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### content_type
```
content_type
```
MIME content type for this image, unconditionally `image/x-emf` for EMF images.
#### default_ext
```
default_ext
```
Default filename extension, always 'emf' for EMF images.
#### from_stream
```
from_stream(stream)
```
Return an Emf instance with header properties parsed from `stream`.
Source code in `src/docx/image/emf.py`
```
@classmethod
def from_stream(cls, stream):
"""Return an |Emf| instance with header properties parsed from `stream`."""
header = cls._read_header(stream)
horz_dpi, vert_dpi = cls._dpi_from_header(header)
inch_width, inch_height = cls._extents_in_inches(header)
px_width = int(round(inch_width * horz_dpi))
px_height = int(round(inch_height * vert_dpi))
return cls(px_width, px_height, horz_dpi, vert_dpi)
```
## exceptions
Exceptions specific the the image sub-package.
### InvalidImageStreamError
Bases: `Exception`
The recognized image stream appears to be corrupted.
### UnexpectedEndOfFileError
Bases: `Exception`
EOF was unexpectedly encountered while reading an image stream.
### UnrecognizedImageError
Bases: `Exception`
The provided image stream could not be recognized.
## gif
### Gif
```
Gif(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `BaseImageHeader`
Image header parser for GIF images.
Note that the GIF format does not support resolution (DPI) information. Both horizontal and vertical DPI default to 72.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### content_type
```
content_type
```
MIME content type for this image, unconditionally `image/gif` for GIF images.
#### default_ext
```
default_ext
```
Default filename extension, always 'gif' for GIF images.
#### from_stream
```
from_stream(stream)
```
Return Gif instance having header properties parsed from GIF image in `stream`.
Source code in `src/docx/image/gif.py`
```
@classmethod
def from_stream(cls, stream):
"""Return |Gif| instance having header properties parsed from GIF image in
`stream`."""
px_width, px_height = cls._dimensions_from_stream(stream)
return cls(px_width, px_height, 72, 72)
```
## helpers
### StreamReader
```
StreamReader(stream, byte_order, base_offset=0)
```
Wraps a file-like object to provide access to structured data from a binary file.
Byte-order is configurable. `base_offset` is added to any base value provided to calculate actual location for reads.
Source code in `src/docx/image/helpers.py`
```
def __init__(self, stream, byte_order, base_offset=0):
super(StreamReader, self).__init__()
self._stream = stream
self._byte_order = LITTLE_ENDIAN if byte_order == LITTLE_ENDIAN else BIG_ENDIAN
self._base_offset = base_offset
```
#### read
```
read(count)
```
Allow pass-through read() call.
Source code in `src/docx/image/helpers.py`
```
def read(self, count):
"""Allow pass-through read() call."""
return self._stream.read(count)
```
#### read_byte
```
read_byte(base, offset=0)
```
Return the int value of the byte at the file position defined by self.\_base_offset + `base` + `offset`.
If `base` is None, the byte is read from the current position in the stream.
Source code in `src/docx/image/helpers.py`
```
def read_byte(self, base, offset=0):
"""Return the int value of the byte at the file position defined by
self._base_offset + `base` + `offset`.
If `base` is None, the byte is read from the current position in the stream.
"""
fmt = "B"
return self._read_int(fmt, base, offset)
```
#### read_long
```
read_long(base, offset=0)
```
Return the int value of the four bytes at the file position defined by self.\_base_offset + `base` + `offset`.
If `base` is None, the long is read from the current position in the stream. The endian setting of this instance is used to interpret the byte layout of the long.
Source code in `src/docx/image/helpers.py`
```
def read_long(self, base, offset=0):
"""Return the int value of the four bytes at the file position defined by
self._base_offset + `base` + `offset`.
If `base` is None, the long is read from the current position in the stream. The
endian setting of this instance is used to interpret the byte layout of the
long.
"""
fmt = "L"
return self._read_int(fmt, base, offset)
```
#### read_short
```
read_short(base, offset=0)
```
Return the int value of the two bytes at the file position determined by `base` and `offset`, similarly to `read_long()` above.
Source code in `src/docx/image/helpers.py`
```
def read_short(self, base, offset=0):
"""Return the int value of the two bytes at the file position determined by
`base` and `offset`, similarly to ``read_long()`` above."""
fmt = b"H"
return self._read_int(fmt, base, offset)
```
#### read_str
```
read_str(char_count, base, offset=0)
```
Return a string containing the `char_count` bytes at the file position determined by self.\_base_offset + `base` + `offset`.
Source code in `src/docx/image/helpers.py`
```
def read_str(self, char_count, base, offset=0):
"""Return a string containing the `char_count` bytes at the file position
determined by self._base_offset + `base` + `offset`."""
def str_struct(char_count):
format_ = "%ds" % char_count
return Struct(format_)
struct = str_struct(char_count)
chars = self._unpack_item(struct, base, offset)
unicode_str = chars.decode("UTF-8")
return unicode_str
```
#### tell
```
tell()
```
Allow pass-through tell() call.
Source code in `src/docx/image/helpers.py`
```
def tell(self):
"""Allow pass-through tell() call."""
return self._stream.tell()
```
## image
Provides objects that can characterize image streams.
That characterization is as to content type and size, as a required step in including them in a document.
### Image
```
Image(
blob: bytes,
filename: str,
image_header: BaseImageHeader,
)
```
Graphical image stream such as JPEG, PNG, or GIF with properties and methods required by ImagePart.
Source code in `src/docx/image/image.py`
```
def __init__(self, blob: bytes, filename: str, image_header: BaseImageHeader):
super(Image, self).__init__()
self._blob = blob
self._filename = filename
self._image_header = image_header
```
#### blob
```
blob
```
The bytes of the image 'file'.
#### content_type
```
content_type: str
```
MIME content type for this image, e.g. `'image/jpeg'` for a JPEG image.
#### filename
```
filename
```
Original image file name, if loaded from disk, or a generic filename if loaded from an anonymous stream.
#### px_width
```
px_width: int
```
The horizontal pixel dimension of the image.
#### px_height
```
px_height: int
```
The vertical pixel dimension of the image.
#### horz_dpi
```
horz_dpi: int
```
Integer dots per inch for the width of this image.
Defaults to 72 when not present in the file, as is often the case.
#### vert_dpi
```
vert_dpi: int
```
Integer dots per inch for the height of this image.
Defaults to 72 when not present in the file, as is often the case.
#### orientation
```
orientation: int
```
The EXIF `Orientation` of this image, 1 through 8.
1 when the image declares none, which is every format but JPEG and TIFF and most files even of those. The eight values are: 1 normal, 2 mirrored, 3 rotated 180°, 4 mirrored and 180°, 5 mirrored and 90° counter-clockwise, 6 rotated 90° clockwise, 7 mirrored and 90° clockwise, 8 rotated 90° counter-clockwise. 6 and 8 are the common ones, and the two that exchange width and height.
#### is_rotated
```
is_rotated: bool
```
`True` when this image's EXIF orientation exchanges its width and height.
#### px_display_width
```
px_display_width: int
```
Width in pixels *as displayed*, honouring the EXIF orientation.
The same as px_width unless the orientation is a quarter turn, in which case the two are exchanged. This is the one to scale from: a portrait photo off a phone is stored landscape with an `Orientation` of 6, and computing a height from px_width gives an aspect ratio nothing will render at.
#### px_display_height
```
px_display_height: int
```
Height in pixels *as displayed*, honouring the EXIF orientation.
See px_display_width.
#### width
```
width: Inches
```
A Length value representing the native width of the image, calculated from the values of `px_width` and `horz_dpi`.
The *stored* width; see display_width for the width after the EXIF orientation is applied.
#### height
```
height: Inches
```
A Length value representing the native height of the image, calculated from the values of `px_height` and `vert_dpi`.
The *stored* height; see display_height.
#### display_width
```
display_width: Inches
```
The native width of the image as displayed, honouring the EXIF orientation.
The dpi values are exchanged along with the pixel counts, since a quarter turn takes the stored rows to the displayed columns.
#### display_height
```
display_height: Inches
```
The native height of the image as displayed, honouring the EXIF orientation.
#### drawingml_transform
```
drawingml_transform: Tuple[int, bool]
```
The (rotation, flip_h) pair expressing this image's EXIF orientation.
Rotation is in 60000ths of a degree, the unit `a:xfrm/@rot` uses; `flip_h` maps to `@flipH`. `(0, False)` for an image that needs no transform.
The rotation goes in the DrawingML rather than into the pixels: rotating the bytes would mean a JPEG decode/encode dependency this library does not have, would lose quality, and would break the sha1-based part deduplication in `package.py` that keeps one copy of an image used twice.
#### from_blob
```
from_blob(blob: bytes) -> Image
```
Return a new Image subclass instance parsed from the image binary contained in `blob`.
Source code in `src/docx/image/image.py`
```
@classmethod
def from_blob(cls, blob: bytes) -> Image:
"""Return a new |Image| subclass instance parsed from the image binary contained
in `blob`."""
stream = io.BytesIO(blob)
return cls._from_stream(stream, blob)
```
#### from_file
```
from_file(
image_descriptor: str | PathLike[str] | IO[bytes],
)
```
Return a new Image subclass instance loaded from the image file identified by `image_descriptor`, a path (a string or `os.PathLike`) or file-like object.
Source code in `src/docx/image/image.py`
```
@classmethod
def from_file(cls, image_descriptor: str | os.PathLike[str] | IO[bytes]):
"""Return a new |Image| subclass instance loaded from the image file identified
by `image_descriptor`, a path (a string or ``os.PathLike``) or file-like
object."""
if isinstance(image_descriptor, (str, os.PathLike)):
path = os.fspath(image_descriptor)
with open(path, "rb") as f:
blob = f.read()
stream = io.BytesIO(blob)
filename = os.path.basename(path)
else:
stream = image_descriptor
stream.seek(0)
blob = stream.read()
filename = None
return cls._from_stream(stream, blob, filename)
```
#### ext
```
ext()
```
The file extension for the image.
If an actual one is available from a load filename it is used. Otherwise a canonical extension is assigned based on the content type. Does not contain the leading period, e.g. 'jpg', not '.jpg'.
Source code in `src/docx/image/image.py`
```
@lazyproperty
def ext(self):
"""The file extension for the image.
If an actual one is available from a load filename it is used. Otherwise a
canonical extension is assigned based on the content type. Does not contain the
leading period, e.g. 'jpg', not '.jpg'.
"""
return os.path.splitext(self._filename)[1][1:]
```
#### scaled_dimensions
```
scaled_dimensions(
width: int | Length | None = None,
height: int | Length | None = None,
*,
honor_exif_orientation: bool = True,
) -> Tuple[Length, Length]
```
(cx, cy) pair representing scaled dimensions of this image.
The native dimensions of the image are scaled by applying the following rules to the `width` and `height` arguments.
- If both `width` and `height` are specified, the return value is (`width`, `height`); no scaling is performed.
- If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image.
- If both `width` and `height` are `None`, the native dimensions are returned.
The native dimensions are calculated using the dots-per-inch (dpi) value embedded in the image, defaulting to 72 dpi if no value is specified, as is often the case. The returned values are both Length objects.
The *display* dimensions are used, so a photo carrying an EXIF orientation that turns it a quarter is scaled to the aspect ratio it will be rendered at rather than the one its stored pixels have. Pass `honor_exif_orientation=False` for the stored dimensions — for an image whose pixels are already rotated *and* which carries the tag anyway, which some encoders produce and nothing can detect.
Source code in `src/docx/image/image.py`
```
def scaled_dimensions(
self,
width: int | Length | None = None,
height: int | Length | None = None,
*,
honor_exif_orientation: bool = True,
) -> Tuple[Length, Length]:
"""(cx, cy) pair representing scaled dimensions of this image.
The native dimensions of the image are scaled by applying the following rules to
the `width` and `height` arguments.
* If both `width` and `height` are specified, the return value is (`width`,
`height`); no scaling is performed.
* If only one is specified, it is used to compute a scaling factor that is then
applied to the unspecified dimension, preserving the aspect ratio of the image.
* If both `width` and `height` are |None|, the native dimensions are returned.
The native dimensions are calculated using the dots-per-inch (dpi) value
embedded in the image, defaulting to 72 dpi if no value is specified, as is
often the case. The returned values are both |Length| objects.
The *display* dimensions are used, so a photo carrying an EXIF orientation that
turns it a quarter is scaled to the aspect ratio it will be rendered at rather
than the one its stored pixels have. Pass `honor_exif_orientation=False` for the
stored dimensions — for an image whose pixels are already rotated *and* which
carries the tag anyway, which some encoders produce and nothing can detect.
"""
native_width = self.display_width if honor_exif_orientation else self.width
native_height = self.display_height if honor_exif_orientation else self.height
if width is None and height is None:
return native_width, native_height
if width is None:
assert height is not None
scaling_factor = float(height) / float(native_height)
width = round(native_width * scaling_factor)
if height is None:
scaling_factor = float(width) / float(native_width)
height = round(native_height * scaling_factor)
return Emu(width), Emu(height)
```
#### sha1
```
sha1()
```
SHA1 hash digest of the image blob.
Source code in `src/docx/image/image.py`
```
@lazyproperty
def sha1(self):
"""SHA1 hash digest of the image blob."""
return hashlib.sha1(self._blob).hexdigest()
```
### BaseImageHeader
```
BaseImageHeader(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Base class for image header subclasses like Jpeg and Tiff.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### orientation
```
orientation: int
```
The EXIF `Orientation` of this image, 1 through 8; 1 when it declares none.
Only JPEG and TIFF carry the tag; every other format reports 1.
#### content_type
```
content_type: str
```
Abstract property definition, must be implemented by all subclasses.
#### default_ext
```
default_ext: str
```
Default filename extension for images of this type.
An abstract property definition, must be implemented by all subclasses.
#### px_width
```
px_width
```
The horizontal pixel dimension of the image.
#### px_height
```
px_height
```
The vertical pixel dimension of the image.
#### horz_dpi
```
horz_dpi
```
Integer dots per inch for the width of this image.
Defaults to 72 when not present in the file, as is often the case.
#### vert_dpi
```
vert_dpi
```
Integer dots per inch for the height of this image.
Defaults to 72 when not present in the file, as is often the case.
### \_ImageHeaderFactory
```
_ImageHeaderFactory(stream: IO[bytes])
```
A BaseImageHeader subclass instance that can parse headers of image in `stream`.
Most formats are identified by a magic number at a fixed offset. A format that has no such number — SVG, being XML — is identified by a sniffer function instead, and only after every signature has failed to match, so sniffing can never shadow an exact identification.
Source code in `src/docx/image/image.py`
```
def _ImageHeaderFactory(stream: IO[bytes]):
"""A |BaseImageHeader| subclass instance that can parse headers of image in `stream`.
Most formats are identified by a magic number at a fixed offset. A format that has
no such number — SVG, being XML — is identified by a sniffer function instead, and
only after every signature has failed to match, so sniffing can never shadow an
exact identification.
"""
from docx.image import SIGNATURES, SNIFFERS
stream.seek(0)
header = stream.read(_HEADER_SAMPLE_LENGTH)
for cls, offset, signature_bytes in SIGNATURES:
end = offset + len(signature_bytes)
if header[offset:end] == signature_bytes:
return cls.from_stream(stream)
for cls, sniff in SNIFFERS:
if sniff(header):
return cls.from_stream(stream)
raise UnrecognizedImageError
```
## jpeg
Objects related to parsing headers of JPEG image streams.
Includes both JFIF and Exif sub-formats.
### Jpeg
```
Jpeg(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `BaseImageHeader`
Base class for JFIF and EXIF subclasses.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### content_type
```
content_type
```
MIME content type for this image, unconditionally `image/jpeg` for JPEG images.
#### default_ext
```
default_ext
```
Default filename extension, always 'jpg' for JPG images.
### Exif
```
Exif(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `Jpeg`
Image header parser for Exif image format.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### from_stream
```
from_stream(stream)
```
Return Exif instance having header properties parsed from Exif image in `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream):
"""Return |Exif| instance having header properties parsed from Exif image in
`stream`."""
markers = _JfifMarkers.from_stream(stream)
# print('\n%s' % markers)
px_width = markers.sof.px_width
px_height = markers.sof.px_height
horz_dpi = markers.app1.horz_dpi
vert_dpi = markers.app1.vert_dpi
orientation = markers.app1.orientation
return cls(px_width, px_height, horz_dpi, vert_dpi, orientation)
```
### Jfif
```
Jfif(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `Jpeg`
Image header parser for JFIF image format.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### from_stream
```
from_stream(stream)
```
Return a Jfif instance having header properties parsed from image in `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |Jfif| instance having header properties parsed from image in
`stream`."""
markers = _JfifMarkers.from_stream(stream)
px_width = markers.sof.px_width
px_height = markers.sof.px_height
horz_dpi = markers.app0.horz_dpi
vert_dpi = markers.app0.vert_dpi
return cls(px_width, px_height, horz_dpi, vert_dpi)
```
### \_JfifMarkers
```
_JfifMarkers(markers)
```
Sequence of markers in a JPEG file, perhaps truncated at first SOS marker for performance reasons.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, markers):
super(_JfifMarkers, self).__init__()
self._markers = list(markers)
```
#### app0
```
app0
```
First APP0 marker in image markers.
#### app1
```
app1
```
First APP1 marker in image markers.
#### sof
```
sof
```
First start of frame (SOFn) marker in this sequence.
#### from_stream
```
from_stream(stream)
```
Return a \_JfifMarkers instance containing a `_JfifMarker` subclass instance for each marker in `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |_JfifMarkers| instance containing a |_JfifMarker| subclass instance
for each marker in `stream`."""
marker_parser = _MarkerParser.from_stream(stream)
markers = []
for marker in marker_parser.iter_markers():
markers.append(marker)
if marker.marker_code == JPEG_MARKER_CODE.SOS:
break
return cls(markers)
```
### \_MarkerParser
```
_MarkerParser(stream_reader)
```
Service class that knows how to parse a JFIF stream and iterate over its markers.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, stream_reader):
super(_MarkerParser, self).__init__()
self._stream = stream_reader
```
#### from_stream
```
from_stream(stream)
```
Return a \_MarkerParser instance to parse JFIF markers from `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |_MarkerParser| instance to parse JFIF markers from `stream`."""
stream_reader = StreamReader(stream, BIG_ENDIAN)
return cls(stream_reader)
```
#### iter_markers
```
iter_markers()
```
Generate a (marker_code, segment_offset) 2-tuple for each marker in the JPEG `stream`, in the order they occur in the stream.
Source code in `src/docx/image/jpeg.py`
```
def iter_markers(self):
"""Generate a (marker_code, segment_offset) 2-tuple for each marker in the JPEG
`stream`, in the order they occur in the stream."""
marker_finder = _MarkerFinder.from_stream(self._stream)
start = 0
marker_code = None
while marker_code != JPEG_MARKER_CODE.EOI:
marker_code, segment_offset = marker_finder.next(start)
marker = _MarkerFactory(marker_code, self._stream, segment_offset)
yield marker
start = segment_offset + marker.segment_length
```
### \_MarkerFinder
```
_MarkerFinder(stream)
```
Service class that knows how to find the next JFIF marker in a stream.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, stream):
super(_MarkerFinder, self).__init__()
self._stream = stream
```
#### from_stream
```
from_stream(stream)
```
Return a \_MarkerFinder instance to find JFIF markers in `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |_MarkerFinder| instance to find JFIF markers in `stream`."""
return cls(stream)
```
#### next
```
next(start)
```
Return a (marker_code, segment_offset) 2-tuple identifying and locating the first marker in `stream` occuring after offset `start`.
The returned `segment_offset` points to the position immediately following the 2-byte marker code, the start of the marker segment, for those markers that have a segment.
Source code in `src/docx/image/jpeg.py`
```
def next(self, start):
"""Return a (marker_code, segment_offset) 2-tuple identifying and locating the
first marker in `stream` occuring after offset `start`.
The returned `segment_offset` points to the position immediately following the
2-byte marker code, the start of the marker segment, for those markers that have
a segment.
"""
position = start
while True:
# skip over any non-\xFF bytes
position = self._offset_of_next_ff_byte(start=position)
# skip over any \xFF padding bytes
position, byte_ = self._next_non_ff_byte(start=position + 1)
# 'FF 00' sequence is not a marker, start over if found
if byte_ == b"\x00":
continue
# this is a marker, gather return values and break out of scan
marker_code, segment_offset = byte_, position + 1
break
return marker_code, segment_offset
```
### \_Marker
```
_Marker(marker_code, offset, segment_length)
```
Base class for JFIF marker classes.
Represents a marker and its segment occuring in a JPEG byte stream.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, marker_code, offset, segment_length):
super(_Marker, self).__init__()
self._marker_code = marker_code
self._offset = offset
self._segment_length = segment_length
```
#### marker_code
```
marker_code
```
The single-byte code that identifies the type of this marker, e.g. `'à'` for start of image (SOI).
#### segment_length
```
segment_length
```
The length in bytes of this marker's segment.
#### from_stream
```
from_stream(stream, marker_code, offset)
```
Return a generic \_Marker instance for the marker at `offset` in `stream` having `marker_code`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream, marker_code, offset):
"""Return a generic |_Marker| instance for the marker at `offset` in `stream`
having `marker_code`."""
if JPEG_MARKER_CODE.is_standalone(marker_code):
segment_length = 0
else:
segment_length = stream.read_short(offset)
return cls(marker_code, offset, segment_length)
```
### \_App0Marker
```
_App0Marker(
marker_code,
offset,
length,
density_units,
x_density,
y_density,
)
```
Bases: `_Marker`
Represents a JFIF APP0 marker segment.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, marker_code, offset, length, density_units, x_density, y_density):
super(_App0Marker, self).__init__(marker_code, offset, length)
self._density_units = density_units
self._x_density = x_density
self._y_density = y_density
```
#### horz_dpi
```
horz_dpi
```
Horizontal dots per inch specified in this marker, defaults to 72 if not specified.
#### vert_dpi
```
vert_dpi
```
Vertical dots per inch specified in this marker, defaults to 72 if not specified.
#### from_stream
```
from_stream(stream, marker_code, offset)
```
Return an \_App0Marker instance for the APP0 marker at `offset` in `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream, marker_code, offset):
"""Return an |_App0Marker| instance for the APP0 marker at `offset` in
`stream`."""
# field off type notes
# ------------------ --- ----- -------------------
# segment length 0 short
# JFIF identifier 2 5 chr 'JFIF\x00'
# major JPEG version 7 byte typically 1
# minor JPEG version 8 byte typically 1 or 2
# density units 9 byte 1=inches, 2=cm
# horz dots per unit 10 short
# vert dots per unit 12 short
# ------------------ --- ----- -------------------
segment_length = stream.read_short(offset)
density_units = stream.read_byte(offset, 9)
x_density = stream.read_short(offset, 10)
y_density = stream.read_short(offset, 12)
return cls(marker_code, offset, segment_length, density_units, x_density, y_density)
```
### \_App1Marker
```
_App1Marker(
marker_code,
offset,
length,
horz_dpi,
vert_dpi,
orientation=1,
)
```
Bases: `_Marker`
Represents a JFIF APP1 (Exif) marker segment.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, marker_code, offset, length, horz_dpi, vert_dpi, orientation=1):
super(_App1Marker, self).__init__(marker_code, offset, length)
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### horz_dpi
```
horz_dpi
```
Horizontal dots per inch specified in this marker, defaults to 72 if not specified.
#### vert_dpi
```
vert_dpi
```
Vertical dots per inch specified in this marker, defaults to 72 if not specified.
#### orientation
```
orientation
```
EXIF `Orientation` specified in this marker, 1 if it specifies none.
#### from_stream
```
from_stream(stream, marker_code, offset)
```
Extract the horizontal and vertical dots-per-inch value from the APP1 header at `offset` in `stream`.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream, marker_code, offset):
"""Extract the horizontal and vertical dots-per-inch value from the APP1 header
at `offset` in `stream`."""
# field off len type notes
# -------------------- --- --- ----- ----------------------------
# segment length 0 2 short
# Exif identifier 2 6 6 chr 'Exif\x00\x00'
# TIFF byte order 8 2 2 chr 'II'=little 'MM'=big endian
# meaning of universe 10 2 2 chr '*\x00' or '\x00*' depending
# IFD0 off fr/II or MM 10 16 long relative to ...?
# -------------------- --- --- ----- ----------------------------
segment_length = stream.read_short(offset)
if cls._is_non_Exif_APP1_segment(stream, offset):
return cls(marker_code, offset, segment_length, 72, 72)
tiff = cls._tiff_from_exif_segment(stream, offset, segment_length)
return cls(
marker_code,
offset,
segment_length,
tiff.horz_dpi,
tiff.vert_dpi,
tiff.orientation,
)
```
### \_SofMarker
```
_SofMarker(
marker_code, offset, segment_length, px_width, px_height
)
```
Bases: `_Marker`
Represents a JFIF start of frame (SOFx) marker segment.
Source code in `src/docx/image/jpeg.py`
```
def __init__(self, marker_code, offset, segment_length, px_width, px_height):
super(_SofMarker, self).__init__(marker_code, offset, segment_length)
self._px_width = px_width
self._px_height = px_height
```
#### px_height
```
px_height
```
Image height in pixels.
#### px_width
```
px_width
```
Image width in pixels.
#### from_stream
```
from_stream(stream, marker_code, offset)
```
Return an \_SofMarker instance for the SOFn marker at `offset` in stream.
Source code in `src/docx/image/jpeg.py`
```
@classmethod
def from_stream(cls, stream, marker_code, offset):
"""Return an |_SofMarker| instance for the SOFn marker at `offset` in stream."""
# field off type notes
# ------------------ --- ----- ----------------------------
# segment length 0 short
# Data precision 2 byte
# Vertical lines 3 short px_height
# Horizontal lines 5 short px_width
# ------------------ --- ----- ----------------------------
segment_length = stream.read_short(offset)
px_height = stream.read_short(offset, 3)
px_width = stream.read_short(offset, 5)
return cls(marker_code, offset, segment_length, px_width, px_height)
```
### \_MarkerFactory
```
_MarkerFactory(marker_code, stream, offset)
```
Return \_Marker or subclass instance appropriate for marker at `offset` in `stream` having `marker_code`.
Source code in `src/docx/image/jpeg.py`
```
def _MarkerFactory(marker_code, stream, offset):
"""Return |_Marker| or subclass instance appropriate for marker at `offset` in
`stream` having `marker_code`."""
if marker_code == JPEG_MARKER_CODE.APP0:
marker_cls = _App0Marker
elif marker_code == JPEG_MARKER_CODE.APP1:
marker_cls = _App1Marker
elif marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES:
marker_cls = _SofMarker
else:
marker_cls = _Marker
return marker_cls.from_stream(stream, marker_code, offset)
```
## png
### Png
```
Png(
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
)
```
Bases: `BaseImageHeader`
Image header parser for PNG images.
Source code in `src/docx/image/image.py`
```
def __init__(
self,
px_width: int,
px_height: int,
horz_dpi: int,
vert_dpi: int,
orientation: int = 1,
):
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
self._orientation = orientation
```
#### content_type
```
content_type
```
MIME content type for this image, unconditionally `image/png` for PNG images.
#### default_ext
```
default_ext
```
Default filename extension, always 'png' for PNG images.
#### from_stream
```
from_stream(stream)
```
Return a Png instance having header properties parsed from image in `stream`.
Source code in `src/docx/image/png.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |Png| instance having header properties parsed from image in
`stream`."""
parser = _PngParser.parse(stream)
px_width = parser.px_width
px_height = parser.px_height
horz_dpi = parser.horz_dpi
vert_dpi = parser.vert_dpi
return cls(px_width, px_height, horz_dpi, vert_dpi)
```
### \_PngParser
```
_PngParser(chunks)
```
Parses a PNG image stream to extract the image properties found in its chunks.
Source code in `src/docx/image/png.py`
```
def __init__(self, chunks):
super(_PngParser, self).__init__()
self._chunks = chunks
```
#### px_width
```
px_width
```
The number of pixels in each row of the image.
#### px_height
```
px_height
```
The number of stacked rows of pixels in the image.
#### horz_dpi
```
horz_dpi
```
Integer dots per inch for the width of this image.
Defaults to 72 when not present in the file, as is often the case.
#### vert_dpi
```
vert_dpi
```
Integer dots per inch for the height of this image.
Defaults to 72 when not present in the file, as is often the case.
#### parse
```
parse(stream)
```
Return a \_PngParser instance containing the header properties parsed from the PNG image in `stream`.
Source code in `src/docx/image/png.py`
```
@classmethod
def parse(cls, stream):
"""Return a |_PngParser| instance containing the header properties parsed from
the PNG image in `stream`."""
chunks = _Chunks.from_stream(stream)
return cls(chunks)
```
### \_Chunks
```
_Chunks(chunk_iterable)
```
Collection of the chunks parsed from a PNG image stream.
Source code in `src/docx/image/png.py`
```
def __init__(self, chunk_iterable):
super(_Chunks, self).__init__()
self._chunks = list(chunk_iterable)
```
#### IHDR
```
IHDR
```
IHDR chunk in PNG image.
#### pHYs
```
pHYs
```
PHYs chunk in PNG image, or `None` if not present.
#### from_stream
```
from_stream(stream)
```
Return a \_Chunks instance containing the PNG chunks in `stream`.
Source code in `src/docx/image/png.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |_Chunks| instance containing the PNG chunks in `stream`."""
chunk_parser = _ChunkParser.from_stream(stream)
chunks = list(chunk_parser.iter_chunks())
return cls(chunks)
```
### \_ChunkParser
```
_ChunkParser(stream_rdr)
```
Extracts chunks from a PNG image stream.
Source code in `src/docx/image/png.py`
```
def __init__(self, stream_rdr):
super(_ChunkParser, self).__init__()
self._stream_rdr = stream_rdr
```
#### from_stream
```
from_stream(stream)
```
Return a \_ChunkParser instance that can extract the chunks from the PNG image in `stream`.
Source code in `src/docx/image/png.py`
```
@classmethod
def from_stream(cls, stream):
"""Return a |_ChunkParser| instance that can extract the chunks from the PNG
image in `stream`."""
stream_rdr = StreamReader(stream, BIG_ENDIAN)
return cls(stream_rdr)
```
#### iter_chunks
```
iter_chunks()
```
Generate a \_Chunk subclass instance for each chunk in this parser's PNG stream, in the order encountered in the stream.
Source code in `src/docx/image/png.py`
```
def iter_chunks(self):
"""Generate a |_Chunk| subclass instance for each chunk in this parser's PNG
stream, in the order encountered in the stream."""
for chunk_type, offset in self._iter_chunk_offsets():
chunk = _ChunkFactory(chunk_type, self._stream_rdr, offset)
yield chunk
```
### \_Chunk
```
_Chunk(chunk_type)
```
Base class for specific chunk types.
Also serves as the default chunk type.
Source code in `src/docx/image/png.py`
```
def __init__(self, chunk_type):
super(_Chunk, self).__init__()
self._chunk_type = chunk_type
```
#### type_name
```
type_name
```
The chunk type name, e.g. 'IHDR', 'pHYs', etc.
#### from_offset
```
from_offset(chunk_type, stream_rdr, offset)
```
Return a default \_Chunk instance that only knows its chunk type.
Source code in `src/docx/image/png.py`
```
@classmethod
def from_offset(cls, chunk_type, stream_rdr, offset):
"""Return a default _Chunk instance that only knows its chunk type."""
return cls(chunk_type)
```
### \_IHDRChunk
```
_IHDRChunk(chunk_type, px_width, px_height)
```
Bases: `_Chunk`
IHDR chunk, contains the image dimensions.
Source code in `src/docx/image/png.py`
```
def __init__(self, chunk_type, px_width, px_height):
super(_IHDRChunk, self).__init__(chunk_type)
self._px_width = px_width
self._px_height = px_height
```
#### from_offset
```
from_offset(chunk_type, stream_rdr, offset)
```
Return an \_IHDRChunk instance containing the image dimensions extracted from the IHDR chunk in `stream` at `offset`.
Source code in `src/docx/image/png.py`
```
@classmethod
def from_offset(cls, chunk_type, stream_rdr, offset):
"""Return an _IHDRChunk instance containing the image dimensions extracted from
the IHDR chunk in `stream` at `offset`."""
px_width = stream_rdr.read_long(offset)
px_height = stream_rdr.read_long(offset, 4)
return cls(chunk_type, px_width, px_height)
```
### \_ChunkFactory
```
_ChunkFactory(chunk_type, stream_rdr, offset)
```
Return a \_Chunk subclass instance appropriate to `chunk_type` parsed from `stream_rdr` at `offset`.
Source code in `src/docx/image/png.py`
```
def _ChunkFactory(chunk_type, stream_rdr, offset):
"""Return a |_Chunk| subclass instance appropriate to `chunk_type` parsed from
`stream_rdr` at `offset`."""
chunk_cls_map = {
PNG_CHUNK_TYPE.IHDR: _IHDRChunk,
PNG_CHUNK_TYPE.pHYs: _pHYsChunk,
}
chunk_cls = chunk_cls_map.get(chunk_type, _Chunk)
return chunk_cls.from_offset(chunk_type, stream_rdr, offset)
```
## svg
Image header parser for SVG images.
SVG is XML, so there is no fixed-offset magic number to match and no binary header to unpack. Size comes from the `width` and `height` attributes of the root `