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.xmllistsupDownArrowtwice, identically) are generated bytools/DrawingPresets/generate.pyfrom the Part 1 zip (checked against its pinned SHA-256) intoDrawing/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 fromavLstreplace the defaults by name; they are not clamped here, the shape's ownpinguides do that.- Path commands (
moveTo,lnTo,quadBezTo,cubicBezTo,arcTo,close) are scaled by the path's ownw/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,darkenLessare +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:xfrmoff/ext in EMU,rotin 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 firstChoicewhose content is a drawing element is used, else theFallback.
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 aswp: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 inrelativeHeightorder. 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(elsetitle) is the figure's alt text;adec:decorativemakes 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.