Files
craft-libs/crates/drawingml/README.md
Dylan M. Taylor 5ebc719ca2 craft-drawingml: model types for fills, lines, text, tables, charts and diagrams (#901)
* Add craft-drawingml: units, colours, preset shapes, custom geometry and text warps

The parts of DrawingML that need no document model, moved from DeckCraft
(60f91f78): crates/color, crates/geom (preset, warp, Xfrm and the EMU
conversions) and crates/pptx/src/custgeom.rs with CustomPath from
crates/model.

The code is unchanged except that custgeom reads craft-xml trees and writes
to a craft-xml Writer instead of DeckCraft's own XML helper, and matches
elements by local name. The 59 tests of the moved code come with it; six
more cover the custom geometry limits.

* drawingml: evaluate a custom geometry's guide lists once per coordinate space

read_cust_geom rebuilt Guides and evaluated avLst and gdLst again for every
path: up to 1,000 paths times up to 10,000 guides, each name copied and
hashed.

Guides are now cached per path coordinate space (w, h). At most 16 spaces
and 4 MiB of guide names are kept per geometry; paths in further spaces are
left out. The first space is always kept, and a geometry without guides is
not capped.

Ported from storytold/deckcraft#1789. A list counts as having guides only
when it has gd children, as that PR's review asked.

* drawingml: the model types for fills, lines, text, tables, charts and diagrams

Moved from DeckCraft (60f91f78) crates/model/src: style.rs, text.rs,
table.rs, chart.rs, numfmt.rs and trendline.rs, plus Diagram, DiagramNode
and the MediaId and SlideId newtypes from lib.rs.

The files are unchanged except that deckcraft_color:: is crate::color:: and
Fill and TableBorder allow clippy::large_enum_variant. Their 60 tests come
with them. Readers and writers follow.
2026-11-11 18:51:44 -05:00

56 lines
3.7 KiB
Markdown

# craft-drawingml
The parts of DrawingML (ECMA-376 part 1, chapter 20) that presentations, word-processing
documents and spreadsheets share. A `.pptx` slide, a `.docx` drawing and an `.xlsx` drawing all
hold the same `a:` elements; this crate is the one place that knows what they mean.
Nothing here opens a package. Readers take a `craft_xml::El` and writers a `craft_xml::Writer`,
so the caller decides which part the markup comes from. Lengths are points (1/72 inch) as `f64`;
files store EMU (12 700 per point).
| Module | What | Markup |
|---|---|---|
| crate root | `emu_to_pt`, `pt_to_emu`, `Xfrm` (offset, extent, rotation, flips, as an affine), `group_child_affine` | `a:xfrm`, `a:chOff`, `a:chExt` |
| `color` | `Rgba`, `SchemeSlot` and `ColorScheme` (the twelve theme colours and their `bg1`/`tx1` aliases), `ColorTransform` with `apply`, the preset and system colour names | `a:srgbClr`, `a:schemeClr`, `a:prstClr`, `a:sysClr`, `a:lumMod`, `a:tint`, `a:alpha` … |
| `preset` | `build(name, w, h, adjusts)`: the outline of a preset shape as fill and stroke sub-paths, its text rectangle, adjust handles and connection sites; `CATALOG` with labels, categories and default adjusts | `a:prstGeom` |
| `custgeom` | `read_cust_geom` / `write_cust_geom` between `a:custGeom` and `CustomPath` (an `M L C Q Z` path string per sub-path), and `Guides`, the shape guide formula evaluator | `a:custGeom`, `a:gd` |
| `warp` | `warp(name, adjusts, rect)`: the bands and guide curves of a preset text warp, and the mapping of laid-out lines onto them | `a:prstTxWarp` |
## The model
These modules hold types only; their readers and writers are not here yet.
| Module | Types | Markup they stand for |
|---|---|---|
| `style` | `ColorRef` (a base colour plus transforms, resolved against a `ColorScheme`), `Fill`, `Gradient`, `PictureFill`, `Line`, `Effects`, `ShapeStyle` | `a:solidFill`, `a:gradFill`, `a:blipFill`, `a:pattFill`, `a:ln`, `a:effectLst`, `p:style` |
| `text` | `TextBody`, `Paragraph`, `ParaProps`, `Run`, `RunProps`, `ListStyle`, hyperlinks and actions | `a:txBody`, `a:bodyPr`, `a:lstStyle`, `a:p`, `a:r` |
| `table` | `Table`, `Row`, `Cell`, `TableStyleDef` and the built-in table styles | `a:tbl`, `a:tblStyleLst` |
| `chart` | `Chart`, `Series`, axes, data labels, trendlines and error bars, with the values they compute | `c:chartSpace`, `cx:chartSpace` |
| `numfmt` | `format(code, value, lang)`: number format codes as charts and cells use them | `c:numFmt` |
| `diagram` | `Diagram`, `DiagramNode` | `dgm:dataModel` |
A picture or sound is a `MediaId`; whoever owns the package keeps the bytes. Themes are not here yet.
## Preset shapes and text warps
Preset names are the file's (`roundRect`, `rightArrow`, `textArchUp`) so files round-trip. The
outlines are constructed in Rust from what each shape looks like and from the meaning of its
adjust values; no geometry table is copied. Adjust values use the file convention: ratios in
1/100 000 of the shorter side (or of the width or height), angles in 1/60 000 degree.
```rust
let g = craft_drawingml::preset::build("roundRect", 200.0, 100.0, &[25_000.0]).unwrap();
assert!(!g.paths.is_empty());
assert!(g.text_rect.width() < 200.0);
```
## Hostile input
`read_cust_geom` reads at most 1 000 paths per geometry, 100 000 commands per path and 5 000
guides per list, keeps at most 10 000 guide values, and evaluates the guide lists once per
coordinate space, for at most 16 spaces and 4 MiB of guide names. Every evaluated value is finite and within
±1e15; a division by zero is 0. An arc that sweeps a full turn or more draws one turn. `preset::build` and
`warp::warp` take any `f64` and return `None` or a finite outline. Each limit has a test.
The types derive `serde` traits with the field names the apps' own document files already use.