Files
craft-libs/crates/drawingml
Dylan M. Taylor f3fe9de956 craft-docx: DrawingML colours through craft-drawingml; its preset table takes WordCraft's names (#1049)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* craft-docx: WordCraft's crates/docx as it is at wordcraft 07f97737

The source, unchanged, so the next commit shows only what the move changes.
This commit does not build on its own (it still names wordcraft-doc and
wordcraft-geom, and is not a workspace member yet).

* craft-docx: on craft-wordmodel, craft-opc, craft-xml and craft-offcrypto

- The package (zip reading, part names, relationships, content types,
  zip writing) is craft-opc's; the module keeps WordCraft's bookkeeping
  (passthrough keys, the embedded-part manifest) and its own Rel/Rels shape.
- Parts are parsed with craft-xml's canonical names (the Strict drawing
  names and mc:Choice/@Requires through its hooks), then given the tree
  shape the reader was written against.
- Encryption and Restrict Editing hashes are craft-offcrypto's; this crate
  maps its errors and the w:documentProtection attributes.
- wordcraft-doc is craft-wordmodel; wordcraft-geom's helpers are its geom.

Tests that covered what now lives in craft-opc and craft-offcrypto are left to
those crates; the DIFAT regression is craft-cfb's. Test-only zip use follows
zip 9's fallible names. The attribute-cap test times the read only.

* craft-docx: DrawingML colours through craft-drawingml

Preset colours and the colour transforms of a:srgbClr, a:schemeClr and the
other colour elements are craft-drawingml's (craft_drawingml::color::preset,
ColorTransform, apply); opacity stays here, in percent.

craft-drawingml's preset table takes WordCraft's names (19 more, and the
dk/lt/grey spellings) and is case-insensitive, as WordCraft's was.

Two behaviours change, both to what craft-drawingml and LibreOffice do:
- a:tint and a:shade mix in linear light, not in sRGB bytes (a 50% shade of
  64 128 192 is 44 92 140, not 32 64 96); two tests are updated;
- the transforms WordCraft ignored (hueMod, hueOff, satOff, comp, inv, gray,
  redMod, greenMod, blueMod) are applied.

* craft-drawingml: test the preset names in any case and their dk/lt spellings
2026-11-11 21:33:23 -05:00
..

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.

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.