From d28e34e40e24841d029e6176faf422744ef87aae Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sun, 30 Aug 2026 22:07:52 +0200 Subject: [PATCH] docs(odf): plan the drawing work #771 asks for, in the order the corpus argues for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The umbrella issue lists four sub-topics and nine unparsed shape tags; this writes down which of them are worth building, in what order, and what the test corpus says about each. Five of the nine tags — `draw:polygon`, `draw:polyline`, `draw:caption`, `draw:regular-polygon`, `dr3d:scene` — occur in none of the 124 odf files under `test/data/input`, and `draw:enhanced-geometry` occurs 350 times, which is what puts the enhanced path ahead of the shape list. Two decisions are taken up front because every later stage depends on them: no new `ElementType` (`custom_shape` becomes the general "geometry given rather than named" type, the way `draw:g` already collapses onto `frame`), and a transform is an element accessor rather than a `GraphicStyle` field, since `get_intermediate_style` would otherwise leak a group's transform onto each of its children. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017XABfEapaADjFQCt1vjmDF --- AGENTS.md | 2 +- src/odr/internal/odf/AGENTS.md | 2 + src/odr/internal/odf/PLAN.md | 140 +++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/odr/internal/odf/PLAN.md diff --git a/AGENTS.md b/AGENTS.md index 874b3c743..ca7dab287 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,7 +62,7 @@ bytes ─▶ magic/open_strategy ─▶ DecodedFile ─▶ Document ─▶ Eleme | `src/odr/internal/file_type_table.*` | **The** per-`FileType` table: extensions, MIME types, category, document type, `FileTypeCapabilities`. Every public lookup in `odr.hpp` is a thin forward into it — extend the table, not the lookups. | | `src/odr/internal/html/` | Generic HTML renderer. | | `src/odr/internal/cfb/`, `zip/` | Container formats (CFB, ZIP). | -| `src/odr/internal/odf/` | OpenDocument (odt/ods/odp/odg); see [`odf/AGENTS.md`](src/odr/internal/odf/AGENTS.md). | +| `src/odr/internal/odf/` | OpenDocument (odt/ods/odp/odg); see [`odf/AGENTS.md`](src/odr/internal/odf/AGENTS.md) + [`odf/PLAN.md`](src/odr/internal/odf/PLAN.md). | | `src/odr/internal/ooxml/` | OOXML (docx/pptx/xlsx); see [`ooxml/AGENTS.md`](src/odr/internal/ooxml/AGENTS.md) + per-format docs. | | `src/odr/internal/oldms/` | **Legacy MS binary** (.doc/.ppt/.xls). | | `src/odr/internal/iwork/` | Apple iWork (`.pages`, `.key`, `.numbers`); see [`iwork/AGENTS.md`](src/odr/internal/iwork/AGENTS.md) + [`iwork/PLAN.md`](src/odr/internal/iwork/PLAN.md). | diff --git a/src/odr/internal/odf/AGENTS.md b/src/odr/internal/odf/AGENTS.md index ad5a5de69..b612bb884 100644 --- a/src/odr/internal/odf/AGENTS.md +++ b/src/odr/internal/odf/AGENTS.md @@ -145,6 +145,8 @@ are **tolerated** to keep rendering best-effort. ## Status & open work Feature coverage (element/style checkboxes) is tracked in [`README.md`](README.md). +The drawing gap — transforms, the unparsed shape elements, enhanced geometry and +the embedded chart — has its own staged plan in [`PLAN.md`](PLAN.md) (#771). The structural/foundational gaps, roughly by value: 1. **Editing is text-content only.** No structural edits (insert/delete/move diff --git a/src/odr/internal/odf/PLAN.md b/src/odr/internal/odf/PLAN.md new file mode 100644 index 000000000..d5948cc18 --- /dev/null +++ b/src/odr/internal/odf/PLAN.md @@ -0,0 +1,140 @@ +# ODF drawings plan + +Closing #771: everything an ODF shape carries beyond its bounding box — +`draw:transform`, the shape elements with no parser, `draw:enhanced-geometry`, +and the `draw:object` chart. The module as it stands is in +[`AGENTS.md`](AGENTS.md); the feature checklist is [`README.md`](README.md). +Keep this file honest as stages land, and delete it when they all have. + +## Today + +`parse_any_element_tree` (`odf_parser.cpp`) knows nine drawing tags — +`draw:frame`, `draw:image`, `draw:rect`, `draw:line`, `draw:circle`, +`draw:custom-shape`, `draw:text-box`, `draw:g`, `draw:a`. Everything else falls +through its final `return {null_element_id, …}` and vanishes with its subtree. +`draw:custom-shape` reaches `html::translate_custom_shape` as a positioned +`
` with fill and stroke: the box, never the shape. `draw:transform` is not +read, so a rotated shape draws unrotated. + +## The corpus + +Every number below is `` fallback stays for a shape with no geometry. + +Emitting a path is not "passing the file's markup through": `svg:d` is parsed +into commands and re-serialised by us, so nothing the file wrote reaches the +output as live markup. + +**A transform is not a style.** `get_intermediate_style` walks the *element* +parent chain and overrides down it, so a `GraphicStyle` field would leak a +group's transform onto every child — and CSS already composes a transform +through the nesting. `draw:transform` is therefore an accessor on the shape +handles, next to `x()`/`y()`, not a style property. + +**A transform is composed, not passed through.** ODF angles are radians and CSS +wants degrees, so the list has to be read anyway. It composes to +`internal::util::math::Transform2D`'s shape — four unitless numbers and a +translation that carries a length — which is also what `matrix(a b c d e f)` +means in 19.228. + +**The SVM replacement image stays.** For `draw:object` it is the fallback when +the chart cannot be read, and it is what a producer that wrote no chart part +leaves behind. + +## Stages + +Each stage is one pull request, stacked on the one before. + +### 1 — `draw:transform` + +`DrawingTransform` in the public header; `transform()` on `Frame`, `Rect`, +`Line`, `Circle` and `CustomShape`; the odf adapter reads and composes the +attribute; the renderer writes `transform` + `transform-origin:0 0`. +Visible on 4 files. Closes the `transform` box in `README.md`. + +### 2 — the missing shape elements + +The tag → type table above, the geometry conversions, `path()`/`view_box()` on +`CustomShape`, and the renderer branch that draws a path. `translate_circle` +becomes an `` while it is being touched — it writes `r="50%"` today, +which is wrong for every non-square box. + +### 3 — `draw:enhanced-path` and `draw:equation`, parser only + +The formula mini-language (`$N` modifiers, `?fN` references, the eleven +functions the corpus uses plus the rest of 20.36) and the path grammar +(`M L C Z N U X Y` plus the commands 19.145 defines and the corpus does not +use). Pure functions over strings, unit-tested from string literals, no +rendering and no element-model change. The largest single piece. + +### 4 — enhanced geometry, rendered + +Stage 3 wired into `CustomShape::path()`: `svg:viewBox`, `draw:modifiers`, +`draw:mirror-vertical` / `-horizontal`, `draw:text-areas` for where the shape's +text goes. Closes #159. This is the stage that regenerates reference output. + +### 5 — `draw:object` charts + +`` in the embedded part, rendered from its series, axes and +`table:table` of plotted data, with the SVM replacement kept as the fallback. +Closes #179. Large enough to deserve splitting again if it grows; it shares +nothing with stages 1–4 but the `draw:frame` it hangs off. + +## Not scoped + +- **`dr3d:scene`** — a 3-D scene, occurring nowhere in the corpus. +- **`draw:object-ole`** (2 occurrences) — an OLE blob, not ODF markup; what is + readable there is the replacement image we already draw. +- **Glue points and connector routing.** `draw:connector` carries `svg:d` + written by the producer, and drawing that is the whole win. Re-routing a + connector between the shapes it names is layout, not decoding. +- **Editing any of this.** The editor is text-content only (`AGENTS.md`).