Recursive AI

DrawingML rendering

All onedocument docs

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

CallUse
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.
DrawingContextThe 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

FillPainted as
solida path fill
gradient, lineara linear gradient along lin/@ang (scaled to the box when scaled=1)
gradient, patha radial gradient from fillToRect; circle is round, rect and shape are elliptical
patternan 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
picturethe cropped picture (srcRect) stretched into fillRect, or tiled (tile: offset, scale) under a clip. A negative crop pads the picture.
none / grpFillnothing (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

EffectPainted as
outerShdwa drop-shadow filter (offset dist along dir, Gaussian sigma = blurRad/2)
glowa 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 overlaynot drawn; drawing.effect-skipped
scene3d, sp3dthe 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):

HookUse
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.