M10: Presentation (.pptx) export (#1137) (#1162)

* feat: presentation (.pptx) export (#1137)

- Implement OpenXML PresentationML (.pptx) generator in crates/export/src/pptx.rs:
  - Generate [Content_Types].xml, _rels/.rels, core properties, presentation.xml
  - Build slide master, blank slide layout, and DrawingML theme
  - Render slides per page: largest text block as title shape, remaining paragraphs as body text box with font size, bold, italic, and color formatting
  - Embed images with proper DrawingML picture shapes and relationships
  - Generate DrawingML table shapes with column grid and span merging
- Wire OfficeFormat::Pptx into crates/engine/src/compare.rs and export_office
- Register export.pptx command and mark Presentation (.pptx) Ready in catalog
- Support pptx in automation doc_export_office tool
- Wire export.pptx into UI commands and file save dialog
- Add unit and integration tests across crates/export, engine, automation, and ui-egui
- Update create.export-pptx parity feature evidence and docs

* pptx export: saturating slide positions, matching cell-border tags, theme ea/cs fonts, package test

- Heights and the running vertical position use clamped/saturating arithmetic, so a hostile
  text size or image rectangle (1e300, NaN, inf) can't overflow i64; the item loop uses get().
- The table cells' a:lnR and a:lnT borders were closed with </a:lnL>, which made every slide
  with a table malformed XML; they now close with their own tags.
- The theme's major/minor fonts carry the schema-required a:ea and a:cs children.
- New test walks the produced zip entry by entry (sizes, inflation, central directory), checks
  every XML part's tags balance, the required parts, content-type overrides, relationship
  targets and that no placeholders are used; a hostile-sizes test covers the overflow.
- Test literals gain main's Block/Image rotation and size fields.
- Parity notes say what is still unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: KrzysztofC <krzysztofc@localhost-live.lan>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Krzysztof Cieślik
2026-10-11 21:55:25 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 KrzysztofC Brandon Thomas
parent 0a447ee1ab
commit 84a75eae1b
36 changed files with 837 additions and 42 deletions
+20 -20
View File
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "23b1e78d022b8d61e529010170f239d3231aa7c8356aca053ed1daa0c6c7e017"
sha256 = "af63461265989c9ec1372693a7b69eb654bc7bf27d48cb2e6110f0b28b99d08e"
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "74e0593b46d5a230364bbf5d11d9b3affc5825dc72c20237e3ee99f6af7b423f"
sha256 = "afb80c70ebc3283e37430fb70e8f38e79cf6cde5dc1f9c15f33b2e60888541dc"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "22b10da7793475fad662e27f6e62eea4f1eb1aa678ff65f7914943b1a7e88b88"
sha256 = "38597523f35334a0d859e35d4d100a3681330b4d8344343e6bc4f20e5e7b31f3"
[[asset]]
path = "crates/ui-egui/src/i18n/cs.tsv"
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "2281deb36a391814012b624cff87c3cbd14f13592a437f84ddf6a5046f2ef6cf"
sha256 = "9a0341426c2cade8e972a0b1ab8c8fdaecf4855e23fdbebf3085f46c7671ee53"
[[asset]]
path = "crates/ui-egui/src/i18n/pt-br.tsv"
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c371535d71a4d6b0f8984b74fc75ea81f1b1ae4683ed5f308ef73b1087c3c738"
sha256 = "200a9a98b5768ad6ca902ed8216ad1bc7726ea31c5eabafb0b3093b0065c8d3c"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f1cc6dd86be23dea0cd07b1984c4d83991b80b5d8ece9296239a64cc1eddf58b"
sha256 = "5e7b00f52143013d994c237ede8e0103f077680793875877aed3450318f5d33f"
[[asset]]
path = "crates/ui-egui/src/i18n/fr.tsv"
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "27cc77eac03ecf3b75fcd24b458108fa6ebf499c8f83948a434e2b2af0318d1a"
sha256 = "90bbf75d0531a72691bed36064ca76d74171349459933ea98b408b66b0e4b82b"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "80e406040f5a1dcc50ba9c6eb9f46da034aebbc86e41fd358a969cdcb9435f35"
sha256 = "78167dfeda59c04b37506677888070e3232ea03bac1d5f9e1758d138bf5d1c4b"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "0e45cd5fd8128696a5c8dcb00925e9e29f14d4ad7f2c8df8932ed2f708a8f67c"
sha256 = "9291b3d848744e956a3b4bba2ac8f861215bd79da786ab6df70e3d1c11cd27fc"
[[asset]]
path = "crates/ui-egui/src/i18n/it.tsv"
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "4a02931d430cc289627af28f2621e057216762c9522131c91900e455597ba749"
sha256 = "c180bc5e82eed9d1659756e90b82d9cf21541f0e5d353043e5aa7995a77cee14"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f25b2a2752ba525c95fd14ee0e0914c6a1021a3516ea4d5d5ead727467b349b6"
sha256 = "a6e48cd058650a8579d030b250d078e5f20b8769bebe0d063bd70367f1958f9f"
[[asset]]
path = "crates/ui-egui/src/i18n/ru.tsv"
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "e35884fa177766b33fa818662c22ceac23e90cfb0b8c8c0f3e45bdee91635078"
sha256 = "691bb26d2243788a1ad174a564afb0802b923a74139b8b282ee101380e572a34"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "544bfb5eeab67471532e888521fedff27abdb43aead1d083b136a287ef0ce668"
sha256 = "2e5aab0e55a9ce8cdaead8a474aba9a2dda60d619c4b8612634ee3db2d8788d2"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "7181163d2bc168a7a8db0be37c9aa097f562a4f4982ee9026c03811aaf686a84"
sha256 = "24e3c057eb25624b397fdcda5bca8d4e505b1edc660d6d18f6e7cf87a55097bd"
[[asset]]
path = "crates/ui-egui/src/i18n/kk.tsv"
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "47b1c5ac4fbae4a1bbcc49ec716762aa3510b64001dd6474682599fd801570d7"
sha256 = "0a4e6e85e4879e5fec6fbc08ac2f3ff0c70350ae9002fad1cfac0305fccb9fdb"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "17fc33cbec7335295ced83f0584baa3ec057cb318ffc812c0d5c4abe08b8dfbd"
sha256 = "76b83c9c58e28b5ea03a4564bcf993c8590a8c3400671206fd3af8c1e5b92f65"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "289dd18eb4f1ecfba4ec500b2ecfdb2fec4645b5f4452b2d056e39ec00508499"
sha256 = "ea2401d850a463d03beb5cce9628fd49ad64ec82dc676bca3114748fe15b9e59"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d95fca18a279910e9a3b52edd99711510afa94bdc41604a10c662fcb2272e851"
sha256 = "7db3c7688833bf001603904d98fda2eabc1c3af7a535c8ac52038c3b5deaa3a3"
[[asset]]
path = "crates/ui-egui/src/i18n/tr.tsv"
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "564f9cbc4e7cb9e8e9bc7263a8951274bcae6be572444fcecb4577441bfd4f24"
sha256 = "65ca67b2281a261285aed4e143c9cc5a06b14d8dc4a001acb88e8d067d61d9f3"
[[asset]]
path = "assets/icons/accessibility.svg"
@@ -3339,4 +3339,4 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "b536e21ac698b4b581c5078d4278c7fb81cce3215d32cb370d3dedfa09837fff"
sha256 = "74cf01a92ad951fc6cf85cacfdca5f0723c166971a03edd1b677ae0cfd109e99"