---
title: "DrawingML rendering"
url: https://recursiveai.org/onedocument/docs/drawingml-rendering/
description: "How shapes, pictures and groups are painted. One painter serves Word drawings, slide shapes and worksheet drawings…"
version: "1.0.0"
source: https://github.com/recursive-ai-oss/onedocument-csharp/blob/a85720e075c783fc596641d8586ee126cae088c5/docs/drawingml-rendering.md
---

# DrawingML rendering

How shapes, pictures and groups are painted. One painter serves Word drawings, slide shapes and worksheet drawings:
`RecursiveAI.OneDocument.Rendering.Drawing.DrawingPainter`. It reads the shape elements (`wps:wsp`, `p:sp`, `p:cxnSp`, `pic:pic`,
`p:pic`, `wpg:wgp`, `p:grpSp`) and never asks which application they came from. Everything is painted into a OneRender display list
in CSS pixels (1 pt = 4/3 px), tagged as described at the end.

## Entry points

| Call | Use |
|---|---|
| `DrawingPainter.Draw(list, nextId, drawing, box, context, altText, decorative)` | One object at a place chosen by the caller (`box`, in points). `drawing` is a `w:drawing`, an `a:graphic`, or a shape element. The box replaces the extents of the outermost shape; its rotation and flips stay. Writes a `Figure` tag with the alt text, or an `Artifact` tag when decorative. |
| `DrawingPainter.DrawShapes(list, nextId, tree, originX, originY, context)` | The children of a shape tree (`p:spTree`, a group); each shape sits where its own transform puts it. Used for slides and worksheet drawing layers. |
| `DrawingContext` | The theme and colour map (`ColorResolver`), the shared `DrawingImages`, a callback giving the bytes of the picture a relationship id names, the scope that keys the image cache, the `Notes` list, and the `TextPainter` hook. |

A chart frame (`c:chart` or `cx:chart` with an `r:id`, in a Word drawing or a slide/worksheet `graphicFrame`) is painted by the
`DrawingContext.ChartPainter` the host installs, at the frame's box in CSS pixels; the Word renderer installs one that loads the chart
part through `ChartHost.Load` and paints it with `ChartHost.Paint` (see `charts.md`). Without a painter, or for a frame that is neither a
chart nor a diagram, the painter notes `drawing.graphic-frame`; a chart that cannot be read notes `drawing.chart-not-painted`. A metafile picture
(EMF or WMF, recognised by its bytes) is drawn through `PictureHook.TryPaint` (see `metafiles.md`), clipped to the shape's outline and
cropped by `srcRect`; its bitmaps share the drawing's image handles. When the hook cannot draw it, the usual bitmap path runs and ends
in the placeholder (`drawing.picture-undecodable`). Metafile defects arrive as notes `drawing.metafile-<defect code>`.

A SmartArt frame (`dgm:relIds` in a Word drawing, a slide or worksheet `graphicFrame`) goes the same way to
`DrawingContext.DiagramPainter` (`DiagramHost.CreatePainter`, see `smartart.md`): the stored `dsp:drawing` or the shapes the layout
engine makes are painted at the frame's box through `DrawShapes`, so they are ordinary shapes with their text. A diagram that cannot be
read notes `drawing.diagram-not-painted`. A shape's own text rectangle (`dsp:txXfrm`, [MS-ODRAWXML] §2.3.1) replaces the geometry's
text rectangle when present.

## Geometry

- The 186 preset geometries of ECMA-376 Part 1 §20.1.9.18 (the standard's `presetShapeDefinitions.xml` lists `upDownArrow` twice,
  identically) are generated by `tools/DrawingPresets/generate.py` from the Part 1 zip (checked against its pinned SHA-256) into
  `Drawing/Generated/PresetShapeData.g.cs`. Never edit that file.
- `GeometryDefinition.Preset(name)` / `FromCustomGeometry(a:custGeom)` give a definition; `Evaluate(width, height, adjust)` gives
  paths in points plus the text rectangle. Guides run in EMU with the formula set of §20.1.1.2. Division by zero, NaN and overflow
  give 0, so every coordinate is finite for any adjust value. Adjust values from `avLst` replace the defaults by name; they are not
  clamped here, the shape's own `pin` guides do that.
- Path commands (`moveTo`, `lnTo`, `quadBezTo`, `cubicBezTo`, `arcTo`, `close`) are scaled by the path's own `w`/`h`. An arc starts at
  the current point; its angles are the direction from the centre, converted to the ellipse parameter, then cut into cubic curves.
- A path that is not closed is still filled (the fill closes it); only stroked paths stay open, which is what lets arrowheads apply.
- A `fill="none"` path is not filled; `stroke="0"` is not stroked; `lighten`, `lightenLess`, `darken`, `darkenLess` are +40 % / +20 %
  toward white and ×0.6 / ×0.8 of the fill colour.
- An unknown preset name draws a rectangle and notes `drawing.unknown-preset`.
- A shape whose placement or size is beyond what a page can hold (a transform entry of 100 million or more) is left out and noted `drawing.shape-out-of-range`.

## Transforms

- `a:xfrm` off/ext in EMU, `rot` in 60000ths of a degree clockwise, flips applied before rotation about the box centre
  (§20.1.7.6).
- Groups map the child space (`chOff`/`chExt`) onto the group's box (`off`/`ext`), then flip and rotate; nesting multiplies. Depth is
  limited to 24 and shapes per drawing to 20,000 (`drawing.limit`).
- A line keeps its width when a group is stretched (the stretch is divided out of the stroke), as Office does; fills and geometry
  stretch.
- `mc:AlternateContent`: the first `Choice` whose content is a drawing element is used, else the `Fallback`.

## Colour

`ColorResolver` resolves `srgbClr`, `scrgbClr`, `hslClr`, `sysClr`, `prstClr` and `schemeClr` (through the colour map and theme, with
`phClr` taking the style reference's colour) and applies every transform of §20.1.2.3 in order: `tint`, `shade`, `comp`, `inv`,
`gray`, `alpha`/`alphaMod`/`alphaOff`, `hue*`, `sat*`, `lum*`, `red*`/`green*`/`blue*`, `gamma`, `invGamma`. `lumMod`/`lumOff` work
in HSL (they reproduce Office's accent palette: accent 1 lighter 80 % / darker 50 % to the stored RGB); `tint` and `shade` work in
linear light, the rule of the standard. Gradients blend in sRGB.

## Fills

| Fill | Painted as |
|---|---|
| solid | a path fill |
| gradient, linear | a linear gradient along `lin/@ang` (scaled to the box when `scaled=1`) |
| gradient, path | a radial gradient from `fillToRect`; `circle` is round, `rect` and `shape` are elliptical |
| pattern | an 8×8 bitmap per preset (one cell = 0.75 pt), tiled under a clip; presets without a rule fall back to a 50 % dither and note `drawing.pattern-approximated` |
| picture | the cropped picture (`srcRect`) stretched into `fillRect`, or tiled (`tile`: offset, scale) under a clip. A negative crop pads the picture. |
| none / `grpFill` | nothing (`grpFill` notes `drawing.group-fill`) |

Style references (`a:style`: `fillRef`, `lnRef`, `effectRef`) take the theme's `fmtScheme` entry (index 1 to 999 fills, 1001 and up
background fills) with the reference's colour as `phClr`. An explicit property replaces the reference item by item.

## Lines

Width (`ln/@w`; default 0.75 pt, never under 0.25 pt), dash presets of §20.1.10.48 (dash and gap in multiples of the width) and
`custDash` (100000 = one width), caps (`flat`, `rnd`, `sq`), joins (`round`, `bevel`, `miter` with its limit), compound lines
(`dbl`, `thickThin`, `thinThick`, `tri`) and arrowheads (`triangle`, `stealth`, `diamond`, `oval`, `arrow`; `sm`/`med`/`lg` are 2, 3
and 5 times the width, never less than 1.5 pt wide) on the open ends. The display list has no stroke item, so a line is a filled
outline (the union of quads, joins and caps). A gradient line takes its first stop's colour (`drawing.line-gradient`); a pattern
line its foreground.

Choices the standard leaves open and that need Office's own output to settle: the compound-line bar proportions, the arrowhead
sizes, the pattern bitmaps, the lighten/darken amounts.

## Effects and 3-D

| Effect | Painted as |
|---|---|
| `outerShdw` | a drop-shadow filter (offset `dist` along `dir`, Gaussian sigma = `blurRad`/2) |
| `glow` | a zero-offset drop-shadow filter in the glow colour (sigma = `rad`/2); used when the shape has no shadow |
| `softEdge`, `reflection`, `innerShdw`, `prstShdw`, `blur`, fill overlay | not drawn; `drawing.effect-skipped` |
| `scene3d`, `sp3d` | the flat two-dimensional shape; `drawing.3d-flattened` |

## Pictures

`a:blip/@r:embed` is looked up through the owning part's relationships; the bytes go through OneImage's decoder via
`DrawingImages` (one cache for every page and backend, keyed by scope and relationship id). Linked pictures (`r:link`) are never
fetched: a placeholder is drawn with `drawing.picture-linked`. A missing part draws the placeholder with `drawing.picture-missing`; a
picture that cannot be decoded (and is not a metafile it can draw) with `drawing.picture-undecodable`. The
placeholder is a grey box with a cross.

Blip effects handled on the bitmap: `grayscl`, `biLevel`, `duotone`, `lum` (brightness and contrast), `alphaModFix`, `clrChange`.
Others (artistic effects, `clrRepl`, `tint`, …) keep the stored bitmap and note `drawing.picture-effect`. Decoded variants are cached
by the effect's XML.

## Text in shapes

`DrawingContext.TextPainter` receives a `ShapeTextRequest`: the text rectangle after the shape's transform and insets, and the
`bodyPr`. The rectangle is the geometry's text rectangle (for example the inscribed rectangle of an ellipse) less the insets
(`lIns`, `tIns`, `rIns`, `bIns`; defaults 0.1 in left and right, 0.05 in top and bottom, ECMA-376 Part 1 §21.1.2.1.1).

The Word renderer installs a painter for `wps:txbx`: each `w:p` of `w:txbxContent` goes through the same paragraph formatter as body
text at the rectangle's width (at its height for vertical text); paragraph spacing adds as in the body, and the block is placed by
`anchor` (`t`, `ctr`, `b`). Vertical text: `vert`, `eaVert` turn the lines 90° clockwise (the first line at the right); `vert270`
turns them 90° counter-clockwise (the first line at the left); `wordArtVert`, `mongolianVert` and `wordArtVertRtl` are treated the same
way and noted (`drawing.text-vertical-approximated`). Text taller than the box is painted past its edge, where Word clips it
(`drawing.text-overflow`). `wrap="none"` wraps at the box width (`drawing.text-wrap-none-approximated`). A table in a text box is laid out at the box's width; a content control inside one is skipped (`drawing.text-block-skipped`). Shape text takes its colour from the runs; the style's `fontRef`
colour is not applied.

Presentation text bodies (`a:txBody`) are painted by the slide renderer, which supplies its own `TextPainter`; without one the
painter notes `drawing.text-body-not-painted`.

## In Word layout

- An inline drawing (`wp:inline`) is an atom of the paragraph: U+FFFC in the text, as wide as `wp:extent`, standing on the baseline
  and raising the line to hold it (`LayoutRunKind.Drawing`).
- An anchored drawing (`wp:anchor`) is positioned and wrapped by the page layout (`layout-floats.md`) and painted from the page's floating
  objects: those behind the text first, the text, then those in front, each in `relativeHeight` order. An object anchored in a table
  cell is placed from the cell (`layoutInCell`, the default) or the page, and text does not wrap around it.
- A drawing with no extent is skipped with `layout.drawing-skipped`; layout continues.
- `docPr/@descr` (else `title`) is the figure's alt text; `adec:decorative` makes it an artifact. A shape's own text is tagged as
  paragraphs inside the figure.

## Notes

Codes on `DrawingContext.Notes` (each once per context): `drawing.limit`, `drawing.unknown-preset`, `drawing.group-fill`,
`drawing.pattern-approximated`, `drawing.line-gradient`, `drawing.effect-skipped`, `drawing.3d-flattened`, `drawing.graphic-frame`,
`drawing.picture-missing`, `drawing.picture-linked`, `drawing.picture-undecodable`, `drawing.picture-effect`,
`drawing.text-body-not-painted`, `drawing.text-not-painted`, `drawing.text-overflow`, `drawing.text-block-skipped`,
`drawing.text-vertical-approximated`, `drawing.text-wrap-none-approximated`, `drawing.chart-not-painted`, `drawing.diagram-not-painted`, `drawing.metafile-*`. `PaintOptions.DrawingNotes` collects them from
`LayoutPainter.Paint`. They are painting notes, not read defects: the file is fine, the renderer approximated.

## Checks

`tests/OneDocument.Rendering.Tests`: all 186 presets give finite paths for default and extreme adjust values at four sizes; hand-checked
rectangle, triangle, rounded rectangle, ellipse, five-point star and custom geometry; colour transforms against the standard's
formulas and Office's accent palette; pixel tests for fill and line, flips, gradients, dashes, compound lines, theme style
references, crop, groups; text anchoring, insets and vertical text measured on the raster. `testdata/drawingml` holds the generated
differential inputs (see its README); the comparison with Office's own PDF runs at the 1.0 gate.

## Hooks for slides

A slide renderer drives the same painter through hooks on `DrawingContext` (all optional; a Word drawing sets none):

| Hook | Use |
|---|---|
| `ShapeResolver(shape)` | Returns `ShapeInheritance`: the shapes a placeholder inherits from, nearest first, and optionally a replacement top edge and height. Position (`a:xfrm`), geometry, fill, line, effects and `p:style` are each taken from the first shape in the list that writes them; the slide's own always comes first. A shape with no geometry anywhere in its chain draws no outline and keeps its text box. |
| `TextPainter(request)` | Paints a shape's text. `ShapeTextRequest` carries the text rectangle after the shape's own insets (`Transform`, `Width`, `Height`) and, additively, the rectangle before insets (`RectX`, `RectY`, `RectWidth`, `RectHeight`) and the shape's matrix (`ShapeMatrix`), so a host that resolves insets through inheritance can place text itself. |
| `FramePainter(frame, list, x, y, w, h)` | Paints a `p:graphicFrame` the painter does not know (a table, a diagram, an embedded object); returning false passes the frame on to the chart painter. |
| `ShapeTagger(shape)` | Says how a shape appears in the structure tree: a figure with alternative text, an artifact, or nothing (a shape whose text paragraphs carry their own tags). |

`DrawingPainter.DrawShapes(..., include)` takes a filter for the top-level shapes (slides skip hidden shapes, and layouts and masters
skip placeholders). `DrawingPainter.DrawBackground` paints a `p:bg` over a rectangle (`bgPr` fills, or `bgRef` into the theme's
background fill styles). How slides use them: `presentation-rendering.md`.
