office-oxide 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. office_oxide-0.1.3/CHANGELOG.md +592 -0
  2. office_oxide-0.1.3/Cargo.lock +671 -0
  3. office_oxide-0.1.3/Cargo.toml +132 -0
  4. office_oxide-0.1.3/LICENSE-APACHE +176 -0
  5. office_oxide-0.1.3/LICENSE-MIT +25 -0
  6. office_oxide-0.1.3/PKG-INFO +33 -0
  7. office_oxide-0.1.3/README.md +451 -0
  8. office_oxide-0.1.3/cbindgen.toml +38 -0
  9. office_oxide-0.1.3/include/office_oxide_c/office_oxide.h +323 -0
  10. office_oxide-0.1.3/pyproject.toml +57 -0
  11. office_oxide-0.1.3/python/office_oxide/__init__.py +48 -0
  12. office_oxide-0.1.3/python/office_oxide/_native.pyi +118 -0
  13. office_oxide-0.1.3/python/office_oxide/py.typed +0 -0
  14. office_oxide-0.1.3/src/cfb/blip.rs +242 -0
  15. office_oxide-0.1.3/src/cfb/directory.rs +185 -0
  16. office_oxide-0.1.3/src/cfb/error.rs +30 -0
  17. office_oxide-0.1.3/src/cfb/header.rs +222 -0
  18. office_oxide-0.1.3/src/cfb/mod.rs +29 -0
  19. office_oxide-0.1.3/src/cfb/reader.rs +635 -0
  20. office_oxide-0.1.3/src/convert_doc.rs +63 -0
  21. office_oxide-0.1.3/src/convert_docx.rs +791 -0
  22. office_oxide-0.1.3/src/convert_ppt.rs +83 -0
  23. office_oxide-0.1.3/src/convert_pptx.rs +415 -0
  24. office_oxide-0.1.3/src/convert_xls.rs +62 -0
  25. office_oxide-0.1.3/src/convert_xlsx.rs +342 -0
  26. office_oxide-0.1.3/src/core/content_types.rs +245 -0
  27. office_oxide-0.1.3/src/core/core_properties.rs +162 -0
  28. office_oxide-0.1.3/src/core/editable.rs +156 -0
  29. office_oxide-0.1.3/src/core/embedded_fonts.rs +119 -0
  30. office_oxide-0.1.3/src/core/error.rs +56 -0
  31. office_oxide-0.1.3/src/core/mod.rs +38 -0
  32. office_oxide-0.1.3/src/core/opc.rs +692 -0
  33. office_oxide-0.1.3/src/core/parallel.rs +21 -0
  34. office_oxide-0.1.3/src/core/properties.rs +413 -0
  35. office_oxide-0.1.3/src/core/relationships.rs +423 -0
  36. office_oxide-0.1.3/src/core/theme.rs +577 -0
  37. office_oxide-0.1.3/src/core/traits.rs +14 -0
  38. office_oxide-0.1.3/src/core/units.rs +277 -0
  39. office_oxide-0.1.3/src/core/xml.rs +381 -0
  40. office_oxide-0.1.3/src/create.rs +1163 -0
  41. office_oxide-0.1.3/src/doc/document.rs +234 -0
  42. office_oxide-0.1.3/src/doc/error.rs +30 -0
  43. office_oxide-0.1.3/src/doc/fib.rs +171 -0
  44. office_oxide-0.1.3/src/doc/images.rs +178 -0
  45. office_oxide-0.1.3/src/doc/mod.rs +21 -0
  46. office_oxide-0.1.3/src/doc/piece_table.rs +371 -0
  47. office_oxide-0.1.3/src/docx/document.rs +29 -0
  48. office_oxide-0.1.3/src/docx/edit.rs +152 -0
  49. office_oxide-0.1.3/src/docx/error.rs +18 -0
  50. office_oxide-0.1.3/src/docx/formatting.rs +1129 -0
  51. office_oxide-0.1.3/src/docx/headers.rs +91 -0
  52. office_oxide-0.1.3/src/docx/hyperlink.rs +21 -0
  53. office_oxide-0.1.3/src/docx/image.rs +81 -0
  54. office_oxide-0.1.3/src/docx/mod.rs +1857 -0
  55. office_oxide-0.1.3/src/docx/numbering.rs +297 -0
  56. office_oxide-0.1.3/src/docx/paragraph.rs +54 -0
  57. office_oxide-0.1.3/src/docx/styles.rs +340 -0
  58. office_oxide-0.1.3/src/docx/table.rs +106 -0
  59. office_oxide-0.1.3/src/docx/text.rs +373 -0
  60. office_oxide-0.1.3/src/docx/write.rs +3556 -0
  61. office_oxide-0.1.3/src/edit.rs +233 -0
  62. office_oxide-0.1.3/src/error.rs +38 -0
  63. office_oxide-0.1.3/src/ffi.rs +1030 -0
  64. office_oxide-0.1.3/src/format.rs +122 -0
  65. office_oxide-0.1.3/src/ir.rs +1178 -0
  66. office_oxide-0.1.3/src/ir_from_markdown.rs +573 -0
  67. office_oxide-0.1.3/src/ir_render.rs +826 -0
  68. office_oxide-0.1.3/src/lib.rs +473 -0
  69. office_oxide-0.1.3/src/ppt/document.rs +307 -0
  70. office_oxide-0.1.3/src/ppt/error.rs +26 -0
  71. office_oxide-0.1.3/src/ppt/images.rs +7 -0
  72. office_oxide-0.1.3/src/ppt/mod.rs +22 -0
  73. office_oxide-0.1.3/src/ppt/records.rs +214 -0
  74. office_oxide-0.1.3/src/ppt/text.rs +302 -0
  75. office_oxide-0.1.3/src/pptx/edit.rs +141 -0
  76. office_oxide-0.1.3/src/pptx/error.rs +15 -0
  77. office_oxide-0.1.3/src/pptx/mod.rs +296 -0
  78. office_oxide-0.1.3/src/pptx/presentation.rs +146 -0
  79. office_oxide-0.1.3/src/pptx/shape.rs +239 -0
  80. office_oxide-0.1.3/src/pptx/slide.rs +2019 -0
  81. office_oxide-0.1.3/src/pptx/text.rs +814 -0
  82. office_oxide-0.1.3/src/pptx/write.rs +1355 -0
  83. office_oxide-0.1.3/src/python.rs +513 -0
  84. office_oxide-0.1.3/src/wasm.rs +58 -0
  85. office_oxide-0.1.3/src/xls/cell.rs +448 -0
  86. office_oxide-0.1.3/src/xls/error.rs +30 -0
  87. office_oxide-0.1.3/src/xls/images.rs +188 -0
  88. office_oxide-0.1.3/src/xls/mod.rs +23 -0
  89. office_oxide-0.1.3/src/xls/records.rs +183 -0
  90. office_oxide-0.1.3/src/xls/sst.rs +248 -0
  91. office_oxide-0.1.3/src/xls/workbook.rs +568 -0
  92. office_oxide-0.1.3/src/xlsx/cell.rs +169 -0
  93. office_oxide-0.1.3/src/xlsx/date.rs +386 -0
  94. office_oxide-0.1.3/src/xlsx/edit.rs +196 -0
  95. office_oxide-0.1.3/src/xlsx/error.rs +18 -0
  96. office_oxide-0.1.3/src/xlsx/mod.rs +1237 -0
  97. office_oxide-0.1.3/src/xlsx/numfmt.rs +515 -0
  98. office_oxide-0.1.3/src/xlsx/shared_strings.rs +284 -0
  99. office_oxide-0.1.3/src/xlsx/styles.rs +627 -0
  100. office_oxide-0.1.3/src/xlsx/text.rs +355 -0
  101. office_oxide-0.1.3/src/xlsx/workbook.rs +220 -0
  102. office_oxide-0.1.3/src/xlsx/worksheet.rs +856 -0
  103. office_oxide-0.1.3/src/xlsx/write.rs +1786 -0
@@ -0,0 +1,592 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.3] - 2026-07-04
9
+
10
+ > Security patch: clears every open RustSec advisory in the dependency tree — an untrusted-XML DoS (quick-xml), two pyo3 soundness/OOB fixes, and a memmap2 unsound-pointer fix — plus Linux aarch64 wheels on PyPI.
11
+
12
+ ### Security
13
+
14
+ - **quick-xml 0.40 → 0.41 (RUSTSEC-2026-0194 / RUSTSEC-2026-0195)** — quick-xml 0.40's `NamespaceResolver::push` performs an unbounded per-`xmlns` heap allocation inside `NsReader` *before* the parse event is returned, so a crafted Office document (DOCX/XLSX/PPTX) declaring many namespaces could exhaust memory and crash the reader — a denial-of-service on untrusted input. Upgraded to quick-xml 0.41, which bounds the allocation. Thanks @smissingham (#58); tracked in #59.
15
+ - **pyo3 0.28 → 0.29 (RUSTSEC-2026-0176 / RUSTSEC-2026-0177)** — fixes an out-of-bounds read in `nth` / `nth_back` for `PyList` and `PyTuple` iterators, and a missing `Sync` bound on `PyCFunction::new_closure` closures, in the Python binding. Via Dependabot (#52).
16
+ - **memmap2 0.9.10 → 0.9.11 (RUSTSEC-2026-0186)** — fixes an unchecked pointer offset (unsoundness). Via Dependabot (#55).
17
+
18
+ ### Packaging
19
+
20
+ - **Linux aarch64 wheels + source distribution on PyPI (#51)** — `pip install office-oxide` on Linux/arm64 (AWS Graviton, Apple-Silicon Docker, Raspberry Pi) failed with "no compatible wheel"; the release workflow now builds `manylinux` aarch64 wheels and an sdist alongside the existing x86_64/macOS wheels. Thanks @regularkevvv.
21
+
22
+ ## [0.1.2] - 2026-05-14
23
+
24
+ > Round-trip fidelity, IR layout features, embedded fonts, XLSX number formatting, and an O(1) style-lookup perf win.
25
+
26
+ ### Performance
27
+
28
+ - **XLSX styles**: cell-format lookups now use a `HashMap`, replacing
29
+ the linear `Vec` scan in `format_cell_value` / `is_date_cell`.
30
+ Per-cell formatting becomes O(1); large styled workbooks parse
31
+ noticeably faster with no API change.
32
+
33
+ ### Round-trip fidelity (PDF → office → PDF)
34
+
35
+ - **Alignment, spacing, footers, and horizontal rules** preserved end-to-end
36
+ through both `to_docx` and `to_pptx` writers.
37
+ - **Images, fonts, and column layouts** preserved across DOCX, PPTX, and
38
+ XLSX. Source-PDF font programs that previously registered as empty
39
+ subsets now embed correctly.
40
+ - **`Element::ThematicBreak`** encoded in PPTX as a centered 30-char run
41
+ of `U+2500 BOX DRAWINGS LIGHT HORIZONTAL`. Downstream PDF renderers
42
+ detect the all-U+2500 content and re-emit a real horizontal rule.
43
+ - **DOCX horizontal rules** recovered from the conventional encoding
44
+ (empty paragraph + `<w:pBdr><w:bottom/>`) back into `Element::ThematicBreak`.
45
+
46
+ ### DOCX
47
+
48
+ - **`<w:framePr>` parsed into IR** as `FramePosition` (twips, page-anchored)
49
+ on both `Paragraph` and `Heading`. Used by layout-preserving paths
50
+ (e.g. pdf_oxide's `to_docx_bytes_layout`).
51
+ - **Floating drawings and vector shapes**: `<wp:anchor>` images plus
52
+ `<wps:wsp>` preset shapes (line, rect) with stroke/fill RGB and
53
+ stroke width round-trip through `DrawingInfo`.
54
+ - **Per-section page sizes** preserved through `to_ir`; multi-section IR
55
+ emits per-section `<w:sectPr>`.
56
+ - **`<w:sz>` preserved** through to IR's `font_size_half_pt`.
57
+ - **Run colour** from `<w:rPr><w:color w:val="RRGGBB"/>` propagated into
58
+ `TextSpan.color` during `to_ir`, so PDF→DOCX→PDF round-trips keep
59
+ coloured text. Only the `ColorRef::Rgb` variant is plumbed today;
60
+ theme / system / `auto` colours still fall through to the renderer
61
+ default (proper resolution needs `theme.xml` threaded into the
62
+ convert path).
63
+ - **Headers and footers** now included in `to_markdown` and `to_ir`
64
+ (previously silently dropped).
65
+ - **Embedded fonts** under `/word/fonts/` exposed on
66
+ `DocxDocument.embedded_fonts`. `strip_embedded_font_filename` recovers
67
+ the original face name from `font_<n>_<face>.<ext>` (fixes greedy
68
+ alphabetic-trim regression where `TeXGyreTermesX-` was returned
69
+ instead of `TeXGyreTermesX-Regular`).
70
+ - **`parse_drawing` decomposed** into focused recursive helpers
71
+ (`parse_inline_or_anchor_body`, `parse_anchor_position`,
72
+ `parse_shape_properties`, etc.) for readability.
73
+ - **Run-level `<w:rFonts w:ascii>` plumbed into `TextSpan.font_name`**;
74
+ `<w:cols>` propagated to `Section.columns`.
75
+
76
+ ### PPTX
77
+
78
+ - **Pagination**: each slide forces a `SectionBreakType::NextPage` so two
79
+ slides never share a rendered page.
80
+ - **Real Title+Body slide layout** emitted by the writer instead of a blank
81
+ layout, so PowerPoint shows placeholder hints in edit mode.
82
+ - **Slide background**: `<p:cSld><p:bg><p:bgPr><a:solidFill><a:srgbClr>`
83
+ parsed into `Slide.background_rgb` and propagated to `Section.background_rgb`.
84
+ - **Positioned text boxes**: shapes with explicit `<a:xfrm>` coordinates
85
+ wrap their content in `Element::TextBox` so downstream renderers can
86
+ place them at absolute EMU coordinates. Zero-size shapes skip the wrapper.
87
+ - **Slide size → page setup**: `<p:sldSz cx=… cy=…>` propagated to each
88
+ section's `PageSetup`.
89
+ - **Run font sizes preserved** via new `TextRun.font_size_hundredths_pt`
90
+ (parsed from `<a:rPr sz="…"/>`).
91
+ - **Run colour preserved** via new `TextRun.color_rgb: Option<[u8; 3]>`
92
+ parsed from `<a:rPr><a:solidFill><a:srgbClr val="RRGGBB"/></a:solidFill>`
93
+ and propagated to `TextSpan.color` in IR. The parser tracks an
94
+ `in_solid_fill` flag so sibling effects (e.g. `<a:hl><a:srgbClr/>`
95
+ for hyperlink colour) don't leak into the run's own fill; non-sRGB
96
+ fills (gradient, scheme colour) fall back to `None`.
97
+ - **Paragraph alignment** parsed from `<a:pPr algn="…"/>` (all five
98
+ variants: `l` / `ctr` / `r` / `just` / `dist`) into
99
+ `TextParagraph.alignment`. **Space-before** parsed from
100
+ `<a:spcBef><a:spcPts val=…/>`.
101
+ - **Title alignment propagation**: `find_title` returns text + first
102
+ paragraph's alignment, seeding both `Section.title` and the synthesised
103
+ level-2 Heading's alignment.
104
+ - **Picture shapes** now carry `embed_rid`, `data`, and `format`
105
+ (resolved via a pre-built media map at parse time, so the parallel
106
+ slide parser doesn't need the OPC reader).
107
+ - **Font embedding** under `/ppt/fonts/`.
108
+ - **Structured chart text extraction**: `<c:chart>` parts parsed into
109
+ per-chart text blocks rendered as `## Chart N` in markdown / search /
110
+ PDF without needing a graphical chart renderer.
111
+ - **Compaction**: consecutive H1/H2 cover-page headings fold into one
112
+ slide instead of fragmenting; long XLSX paragraphs split across cells
113
+ to respect ~32k char-per-cell limits.
114
+ - **Slide cap**: writer caps at ~250 slides (PowerPoint's hard limit).
115
+
116
+ ### XLSX
117
+
118
+ - **Per-worksheet `page_setup`** round-trips via `<pageMargins>` (inches)
119
+ and `<pageSetup>` (paperWidth/paperHeight with mm/cm/in suffix or
120
+ `paperSize` enum 1–13). New `Worksheet.page_setup`.
121
+ - **`numfmt` module** (`crate::xlsx::numfmt`): built-in IDs 0–44 (general,
122
+ fixed, commas, percent, currency, scientific, accounting) and a
123
+ simplified custom format-string parser (multi-section, `[Red]` color
124
+ directives stripped, currency prefix from `[$€-407]`, quoted literal
125
+ suffix, percent and thousands separators). Applied to numeric cells
126
+ during `format_cell_value` and `write_cell_value_fast`.
127
+ - **Font sizes** preserved through IR; long-text single-column sheets
128
+ emit as paragraphs instead of a tall 1-column GFM table.
129
+ - **Unique worksheet names** in `ir_to_xlsx` (duplicates suffixed with
130
+ `_2`, `_3`, …).
131
+ - **Drawings**: `xl/drawings/drawingN.xml` parsed into
132
+ `Worksheet.images` (`WorksheetPicture` with EMU coords + bytes) and
133
+ `Worksheet.text_shapes` (`WorksheetTextShape` for layout-mode text
134
+ boxes from `to_xlsx_bytes_layout`).
135
+ - **Embedded fonts** under `/xl/fonts/`.
136
+
137
+ ### IR enrichment
138
+
139
+ - **New types**: `Shape` (vector shape anchored at absolute EMU coords),
140
+ `ShapeGeom` (`Line`, `Rect`), `FramePosition` (twip-anchored frame).
141
+ - **`Heading`** gains `frame_position` + `alignment`.
142
+ - **`Section`** gains `background_rgb`.
143
+ - **`ParagraphAlignment`** gains the `Distribute` variant.
144
+ - **`Element::Shape(Shape)`** variant for vector shapes.
145
+ - **New helpers**: `first_inline_font_size_pt`, `inline_to_element_block`,
146
+ `build_nested_list` (flat / 2-level / 3-level recursion).
147
+ - **Centralized defaults** in `ir_render::block_default`: ThematicBreak
148
+ renders as `"---"` / `<hr />`; PageBreak / ColumnBreak / Shape are
149
+ invisible in flow; TextBox / Footnote / Endnote recursively render
150
+ children. Adding a new `Element` variant forces a compile error
151
+ in `block_default::default_plain` instead of silent fallthrough.
152
+
153
+ ### Core
154
+
155
+ - **`crate::core::core_properties`**: shared `docProps/core.xml` generator
156
+ used by all three writers. Emits `dc:title`, `dc:creator`, `dc:subject`,
157
+ `dc:description`, `cp:keywords`, `dcterms:created`, `dcterms:modified`
158
+ from the IR's `Metadata`. Empty fields are omitted entirely.
159
+ - **`crate::core::embedded_fonts`**: unified font-embedding helper
160
+ (`write_embedded_fonts`, `sanitize_font_filename`). All three formats
161
+ share the layout `<prefix>font_<n>_<safe_name>.ttf`.
162
+ - **`HalfPoint::from_word_sz` / `from_drawingml_sz` / `to_drawingml_sz` /
163
+ `from_points_rounded`**: cross-format font-size invariants
164
+ (DrawingML hundredths-of-a-point vs WML half-points).
165
+
166
+ ### Dependencies
167
+
168
+ - **`quick-xml` 0.37 → 0.40**: upstream removed `BytesText::unescape()`
169
+ and deprecated `Attribute::unescape_value()` (its replacement
170
+ `normalized_value()` has different semantics — no entity
171
+ unescaping). Migration added two helpers in `core::xml`:
172
+ `unescape_text(BytesText) -> Result<String>` (used by 6 call sites)
173
+ and `unescape_attr_value` (used by 6 call sites, with
174
+ `#[allow(deprecated)]` localised to the helper so call sites stay
175
+ deprecation-free). 535 / 535 tests still pass; clippy clean.
176
+ - **`koffi` 2.16.1 → 2.16.2** in `js/` (patch bump).
177
+
178
+ ### Documentation
179
+
180
+ - **CLI / MCP crate-level docs**: `office_oxide_cli` and
181
+ `office_oxide_mcp` previously opened with `mod commands;` /
182
+ `mod protocol;` and had no crate-level rustdoc. Added a short
183
+ `//!` block plus `#![warn(missing_docs)]` so future items in
184
+ either binary stay documented.
185
+ `RUSTDOCFLAGS="-D missing_docs" cargo doc --workspace --no-deps
186
+ --features parallel,mmap` now passes with zero errors.
187
+
188
+ ### Tests
189
+
190
+ - **+98 unit tests** across the modules touched in this release:
191
+ `core::embedded_fonts`, `core::core_properties`, `core::units`,
192
+ `xlsx::numfmt`, `xlsx::worksheet`, `docx::formatting`, `docx::mod`,
193
+ `pptx::slide`, `ir`, `ir_render`.
194
+ - **535 / 535 tests pass** across default, `--features parallel`,
195
+ `--features mmap`, and `--features parallel,mmap` builds.
196
+ - `cargo fmt` clean. `cargo clippy --workspace --all-targets -- -D warnings`
197
+ clean.
198
+
199
+ ### Bindings
200
+
201
+ - **Python wheel** (maturin, PyO3 0.28) builds cleanly and exposes
202
+ `Document`, `EditableDocument`, `XlsxWriter`, `PptxWriter`,
203
+ `OfficeOxideError`, `create_from_markdown`, `extract_text`,
204
+ `to_markdown`, `to_html`, `version`.
205
+ - **WASM** package (`wasm-pack build --target web/node/bundler`) builds
206
+ cleanly with `--features wasm`.
207
+ - **C#** package bumped to 0.1.2 (csproj only — no API changes).
208
+
209
+ [0.1.2]: https://github.com/yfedoseev/office_oxide/compare/v0.1.1...v0.1.2
210
+
211
+ ## [0.1.1] - 2026-04-30
212
+
213
+ > Richer IR type system, DOCX writer output, improved PPTX/XLSX IR renderers, and writer APIs in all language bindings
214
+
215
+ ### IR — extended type system
216
+
217
+ - **`TextSpan`** gains nine typography fields: `font_name`, `font_size_half_pt`,
218
+ `color`, `highlight`, `underline` (`UnderlineStyle` enum), `vertical_align`
219
+ (`VerticalAlign`: Superscript / Subscript / Baseline), `all_caps`,
220
+ `small_caps`, `char_spacing_half_pt`.
221
+ - **`Paragraph`** gains twelve layout fields: `alignment` (`ParagraphAlignment`:
222
+ Left / Center / Right / Justify / Distribute), `indent_left_twips`,
223
+ `indent_right_twips`, `first_line_indent_twips`, `space_before_twips`,
224
+ `space_after_twips`, `line_spacing` (`LineSpacing`: Auto / Multiple / Exact /
225
+ AtLeast), `background_color`, `border` (`ParagraphBorder`), `keep_with_next`,
226
+ `keep_together`, `page_break_before`, `outline_level`.
227
+ - **`Table`** gains `width_twips`, `column_widths_twips`, `border`
228
+ (`TableBorder`), `alignment` (`TableAlignment`), `indent_left_twips`,
229
+ `cell_padding_twips`, `caption`.
230
+ - **`TableRow`** gains `height_twips`, `allow_break`, `repeat_as_header`.
231
+ - **`TableCell`** gains `background_color`, `border`, `vertical_align`
232
+ (`CellVerticalAlign`), `text_align`, `width_twips`, `padding` (`CellPadding`),
233
+ `text_direction` (`TextDirection`).
234
+ - **`Image`** gains `data`, `format` (`ImageFormat`), `pixel_width`,
235
+ `pixel_height`, `display_width_emu`, `display_height_emu`, `decorative`,
236
+ `positioning` (`ImagePositioning`: Inline or Floating with `FloatingImage`).
237
+ - **`Section`** gains `page_setup` (`PageSetup`), `columns` (`ColumnLayout`),
238
+ `break_type` (`SectionBreakType`), and six header/footer slots
239
+ (`header`, `footer`, `first_page_header`, `first_page_footer`,
240
+ `even_page_header`, `even_page_footer`).
241
+ - **`Metadata`** gains `author`, `subject`, `keywords`, `created`, `modified`,
242
+ `description` (written to `docProps/core.xml`).
243
+ - **New `Element` variants**: `TextBox`, `PageBreak`, `ColumnBreak`,
244
+ `Footnote(Note)`, `Endnote(Note)`, `CodeBlock`.
245
+ - **`List`** gains `start_number`, `style` (`ListStyle`), `level`.
246
+ `ListItem.content` promoted from `Vec<InlineContent>` to `Vec<Element>`
247
+ to allow block-level content (tables, images) inside list items.
248
+ - **`InlineContent`** gains `FootnoteRef` and `EndnoteRef` variants.
249
+ - **New supporting types**: `BorderLine`, `TableBorder`, `ParagraphBorder`,
250
+ `CellPadding`, `FloatingImage`, `HeaderFooter`, `TextBox`, `Note`,
251
+ `FootnoteRef`, `CodeBlock`, `PageSetup`, `ColumnLayout`.
252
+ - **New enums**: `UnderlineStyle`, `ParagraphAlignment`, `LineSpacing`,
253
+ `BorderStyle`, `CellVerticalAlign`, `TableAlignment`, `TextDirection`,
254
+ `ImageFormat`, `ImagePositioning`, `SectionBreakType`, `VerticalAlign`,
255
+ `FloatAnchor`, `TextWrap`, `ListStyle`.
256
+ - All new fields are `Option<_>` or default to `false`/`None` — fully
257
+ backwards compatible; existing callers require only `..Default::default()`
258
+ on struct literals.
259
+
260
+ ### DocxWriter — OOXML emission for all new fields
261
+
262
+ - **Run properties**: `<w:rFonts>`, `<w:sz>`/`<w:szCs>`, `<w:color>`,
263
+ `<w:shd>` (highlight), `<w:u>`, `<w:vertAlign>`, `<w:caps>`,
264
+ `<w:smallCaps>`, `<w:spacing>` (character spacing).
265
+ - **Paragraph properties**: `<w:jc>`, `<w:ind>`, `<w:spacing>` (before/after/
266
+ line), `<w:pBdr>`, `<w:shd>`, `<w:keepNext>`, `<w:keepLines>`,
267
+ `<w:pageBreakBefore>`, `<w:outlineLvl>`.
268
+ - **Table**: `<w:tblW>`, `<w:tblInd>`, `<w:tblBorders>`, `<w:jc>` (table),
269
+ `<w:tblCellMar>`, `<w:tblGrid>`/`<w:gridCol>`.
270
+ - **Table row**: `<w:trHeight>`, `<w:tblHeader>`, `<w:cantSplit>`.
271
+ - **Table cell**: `<w:tcW>`, `<w:gridSpan>`, `<w:vMerge>`, `<w:shd>` (cell),
272
+ `<w:tcBorders>`, `<w:vAlign>`, `<w:textDirection>`, `<w:tcMar>` (per-edge
273
+ padding), cell-level `text_align` propagated to contained paragraphs.
274
+ - **Table caption**: emitted as a `Caption`-styled paragraph before `<w:tbl>`.
275
+ - **Images**: inline `<wp:inline>` and floating `<wp:anchor>` with
276
+ `<wp:wrapSquare>` / `<wp:wrapTight>` / `<wp:wrapThrough>` /
277
+ `<wp:wrapTopAndBottom>` / `<wp:wrapNone>`.
278
+ - **Text boxes**: `<wp:anchor>` + `<wps:txbx>` + `<w:txbxContent>`.
279
+ - **Sections**: `<w:sectPr>` with `<w:pgSz>`, `<w:pgMar>`, `<w:cols>` (uniform
280
+ and per-column widths with separator rule), `<w:type>` (Continuous / NextPage
281
+ / EvenPage / OddPage). Header/footer parts written to `/word/header*.xml` and
282
+ `/word/footer*.xml` with correct relationship entries.
283
+ - **Footnotes / endnotes**: `/word/footnotes.xml` and `/word/endnotes.xml`
284
+ parts; inline `<w:footnoteReference>` / `<w:endnoteReference>` runs with
285
+ `FootnoteReference` / `EndnoteReference` character styles.
286
+ - **`{PAGE}` / `{NUMPAGES}` sentinels** in header/footer text spans emitted as
287
+ `<w:fldChar>` / `<w:instrText>` field runs.
288
+ - **Page break**: `<w:br w:type="page">`. **Column break**: `<w:br
289
+ w:type="column">`.
290
+ - **Code blocks**: `Code`-styled paragraph with preserved whitespace and
291
+ line breaks.
292
+ - **Lists**: `<w:numFmt>` driven by `ListStyle`; `<w:startOverride>` for
293
+ non-1 `start_number`; block-level item content (tables, images) written
294
+ alongside the numbered paragraph.
295
+ - **Metadata**: `author`, `subject`, `keywords`, `description`, `created`,
296
+ `modified` written to `docProps/core.xml` as Dublin Core properties.
297
+
298
+ ### PPTX IR renderer — `ir_to_pptx`
299
+
300
+ - **Rich text runs**: paragraphs and headings now emit styled `<a:r>` runs via
301
+ `add_rich_text()` — bold, italic, font size, color, and font name from
302
+ `TextSpan` are all preserved. Previously all formatting was stripped.
303
+ - **Table elements**: rendered as tab-separated cell text (rows joined with `\n`)
304
+ instead of being silently dropped.
305
+ - **Image elements**: embedded as native PPTX media via the new
306
+ `SlideData::add_image()` API — writes a `<p:pic>` shape with `<p:blipFill>`
307
+ and an OPC media part. PNG, JPEG, and GIF are supported.
308
+ - **CodeBlock elements**: rendered with Courier New font run instead of being
309
+ silently dropped.
310
+ - **Slide dimensions**: first `Section.page_setup` is now forwarded to
311
+ `PptxWriter::set_presentation_size()` (1 twip = 914 400/1 440 EMU), fixing
312
+ clipped output for landscape A4 and other non-16:9 documents.
313
+ - **New `PptxWriter::set_presentation_size(cx, cy)`** method; emits `<p:sldSz>`
314
+ with the correct EMU values instead of always writing the 16:9 default.
315
+
316
+ ### XLSX IR renderer — `ir_to_xlsx`
317
+
318
+ - **Header row styling**: rows with `TableRow.is_header = true` are now written
319
+ with bold weight and a grey (`D3D3D3`) background via `set_cell_styled()`.
320
+ `TableCell.background_color` overrides the default grey when set.
321
+ - **Cell background color**: non-header cells with `TableCell.background_color`
322
+ set now get a solid fill style applied.
323
+ - **Column widths**: `Table.column_widths_twips` is now converted to Excel
324
+ character-width units (`twips × 96 / (1440 × 7)`, clamped 3–80) and written
325
+ via `set_column_width()`.
326
+ - **Merged cells**: `TableCell.col_span` and `row_span` > 1 now emit a
327
+ `<mergeCells>/<mergeCell ref="…"/>` block in the worksheet XML instead of
328
+ being ignored.
329
+ - **New `SheetData::merge_cells(row, col, row_span, col_span)`** method; inserts
330
+ `<mergeCells>` between `</sheetData>` and `</worksheet>`.
331
+ - Row cursor tracks absolute position across all elements in a section, so
332
+ paragraphs and headings interleaved with tables land in the correct rows.
333
+
334
+ ### Writer APIs — all language bindings
335
+
336
+ `XlsxWriter` and `PptxWriter` (previously Rust-only) are now callable from
337
+ every binding layer via a new index-based C FFI surface:
338
+
339
+ **New C FFI symbols** (`include/office_oxide_c/office_oxide.h`):
340
+
341
+ - `office_xlsx_writer_new/free`, `office_xlsx_writer_add_sheet` (returns sheet
342
+ index), `office_xlsx_sheet_set_cell`, `office_xlsx_sheet_set_cell_styled`
343
+ (bold + hex background), `office_xlsx_sheet_merge_cells`,
344
+ `office_xlsx_sheet_set_column_width`, `office_xlsx_writer_save`,
345
+ `office_xlsx_writer_to_bytes`
346
+ - `office_pptx_writer_new/free`, `office_pptx_writer_set_presentation_size`,
347
+ `office_pptx_writer_add_slide` (returns slide index),
348
+ `office_pptx_slide_set_title`, `office_pptx_slide_add_text`,
349
+ `office_pptx_slide_add_image` (PNG/JPEG/GIF bytes + EMU geometry),
350
+ `office_pptx_writer_save`, `office_pptx_writer_to_bytes`
351
+
352
+ **Go** — `XlsxWriter` and `PptxWriter` structs with CGo wrappers and
353
+ `runtime.SetFinalizer` for safe GC.
354
+
355
+ **C# / .NET** — new `OfficeOxide.XlsxWriter` and `OfficeOxide.PptxWriter`
356
+ classes (`IDisposable`); P/Invoke declarations added to `NativeMethods`.
357
+
358
+ **Node.js** — `XlsxWriter` and `PptxWriter` ESM + CJS classes; koffi
359
+ function prototypes; TypeScript `ImageFormat` type and class declarations.
360
+
361
+ **Python** — `XlsxWriter` and `PyO3PptxWriter` PyO3 classes calling Rust
362
+ directly (no C FFI round-trip); exported from `office_oxide` with full type
363
+ stubs in `_native.pyi`.
364
+
365
+ ### Bug fixes
366
+
367
+ - `TableCell.padding` (`CellPadding`) was defined in the IR but silently
368
+ dropped by the writer; now emits `<w:tcMar>` with per-edge twip values.
369
+ - `TableCell.text_align` was defined in the IR but silently dropped; now
370
+ propagated to contained paragraphs (respects pre-existing paragraph
371
+ alignment, so explicit paragraph alignment is never overwritten).
372
+ - `Table.caption` was defined in the IR but silently dropped; now emitted
373
+ as a `Caption`-styled paragraph immediately before the table.
374
+
375
+ ## [0.1.0] - 2026-04-27
376
+
377
+ > Initial public release
378
+
379
+ ### Cross-language bindings
380
+
381
+ - **Rust core** (`office_oxide` on crates.io): unified `Document` handle for
382
+ all six formats, `EditableDocument` for DOCX/XLSX/PPTX editing, format-
383
+ agnostic `DocumentIR`.
384
+ - **Python** (`office-oxide` on PyPI): context-manager `Document` /
385
+ `EditableDocument`, `os.PathLike` support, complete type stubs in
386
+ `_native.pyi` (`Literal` format names, `_Path` alias).
387
+ - **Go** (`github.com/yfedoseev/office_oxide/go`): CGo wrapper over the C FFI
388
+ with idiomatic `Open` / `Close` / error-return API, `go/cmd/install` helper
389
+ that fetches the matching native archive and prints the
390
+ `CGO_CFLAGS` / `CGO_LDFLAGS` to export.
391
+ - **C# / .NET** (`OfficeOxide` on NuGet): `LibraryImport` P/Invoke,
392
+ `IDisposable`, `async/await`, `IsAotCompatible=true`, `IsTrimmable=true`.
393
+ Four `SetCell` overloads + `SetCellEmpty`. Net 8 and net 10 target
394
+ frameworks.
395
+ - **Node.js native** (`office-oxide` on npm): [koffi](https://koffi.dev)-based,
396
+ no node-gyp, ESM + CJS entry points with an `exports` map, TypeScript
397
+ definitions, `Symbol.dispose` support, platform prebuilds staged into
398
+ `prebuilds/<platform>-<arch>/`.
399
+ - **WASM** (`office-oxide-wasm` on npm): three sub-path exports — default
400
+ ESM for bundlers, `office-oxide-wasm/node` for CJS, `office-oxide-wasm/web`
401
+ for native-ESM browser imports. TypeScript definitions shipped.
402
+ - **C FFI** (`include/office_oxide_c/office_oxide.h`): stable
403
+ `office_document_*` / `office_editable_*` surface with out-param error
404
+ codes and explicit memory ownership. Exported from the cdylib + staticlib;
405
+ the substrate that Go, C#, and Node-native link against.
406
+
407
+ ### Tooling
408
+
409
+ - **CLI** (`office-oxide` binary): `text`, `markdown`, `html`, `info`, `ir`
410
+ subcommands.
411
+ - **MCP server** (`office-oxide-mcp` binary): `extract` and `info` tools
412
+ over JSON-RPC 2.0 / stdio.
413
+
414
+ ### Performance
415
+
416
+ - Up to 100× faster than `python-docx`, `openpyxl`, `python-pptx`, `xlrd`.
417
+ - Beats `calamine` on XLSX and all Rust / Python alternatives on .xls.
418
+ - **100% pass rate on valid Office files** (6,062-file corpus: LibreOffice,
419
+ Apache POI, python-pptx, python-docx, Pandoc, etc.). All 97 non-passing
420
+ files are invalid inputs — corrupted ZIPs, missing required parts, malformed
421
+ XML, or non-Office files with Office extensions.
422
+
423
+ ### Documentation & examples
424
+
425
+ - Per-language getting-started guides in [`docs/`](docs/): Rust, Python,
426
+ Go, C#, JavaScript (native), WASM, and C / raw FFI.
427
+ - Per-binding READMEs in [`python/`](python/README.md), [`go/`](go/README.md),
428
+ [`csharp/OfficeOxide/`](csharp/OfficeOxide/README.md),
429
+ [`js/`](js/README.md), [`wasm-pkg/`](wasm-pkg/README.md).
430
+ - Identical `extract` / `replace` / `read_xlsx` demos per language under
431
+ [`examples/`](examples/).
432
+
433
+ ### Release CI
434
+
435
+ - Version parity across `Cargo.toml`, `pyproject.toml`,
436
+ `wasm-pkg/package.json`, `js/package.json`, and
437
+ `csharp/OfficeOxide/OfficeOxide.csproj`.
438
+ - 6-target native-lib build matrix producing `.tar.gz` / `.zip` archives
439
+ with the shared library, static archive, and public header.
440
+ - 3-target WASM build (bundler / nodejs / web) with per-target module-type
441
+ hints so Node + bundlers + browsers each load the right code.
442
+ - NuGet packaging with `runtimes/<rid>/native/` prebuilts, Node
443
+ `prebuilds/<platform>-<arch>/` staging, and a `go/v*` module tag for the
444
+ Go module proxy.
445
+
446
+ ## [0.1.0-draft] - 2026-04-07 (not released)
447
+
448
+ > Internal milestone before the public release above.
449
+
450
+ Single consolidated `office_oxide` crate with modules per format, plus
451
+ companion workspace crates `office_oxide_cli` and `office_oxide_mcp`.
452
+
453
+ ### Added
454
+
455
+ - **Unified API** (`crate::Document`)
456
+ - `Document::open()`, `Document::from_reader()`, `plain_text()`,
457
+ `to_markdown()`, `to_html()`, `to_ir()`, `format_name()`
458
+ - Format auto-detection from file extension for all 6 formats
459
+ - `as_docx()` / `as_xlsx()` / `as_pptx()` / `as_doc()` / `as_xls()` /
460
+ `as_ppt()` escape hatches for format-specific types
461
+ - Convenience functions: `extract_text()`, `to_markdown()`, `to_html()`
462
+ at crate root
463
+
464
+ - **Format-agnostic IR** (`crate::ir::DocumentIR`)
465
+ - Sections, Elements (Heading, Paragraph, Table, List, Image,
466
+ ThematicBreak), serializable to/from JSON
467
+ - DOCX→IR: heading detection via outline level + style resolution,
468
+ list grouping, table vMerge → row_span
469
+ - XLSX→IR: worksheets → sections, cell grids → tables, first row as
470
+ header
471
+ - PPTX→IR: slides → sections, spatial sort, title placeholder → section
472
+ title
473
+ - IR renderers: `plain_text()` and `to_markdown()`
474
+
475
+ - **OOXML module — `crate::docx`** (36 tests)
476
+ - SAX-style parsing of `document.xml`, `styles.xml`, `numbering.xml`
477
+ - Hyperlink resolution, heading detection, list grouping
478
+ - Plain text, Markdown, HTML extraction
479
+
480
+ - **OOXML module — `crate::xlsx`** (57 tests)
481
+ - Shared string table, cell parsing, 1900 date system with Lotus bug
482
+ - Built-in + custom number format detection
483
+ - CSV (RFC 4180), Markdown (pipe tables), HTML output
484
+ - Rich-text and style support
485
+
486
+ - **OOXML module — `crate::pptx`** (40 tests)
487
+ - Slide parsing with spatial sort, shape types (AutoShape, Picture,
488
+ Group, GraphicFrame, Connector), text body extraction
489
+ - Notes slide support, inline hyperlink resolution
490
+ - Plain text, Markdown, HTML extraction
491
+
492
+ - **Legacy module — `crate::cfb`** (18 tests)
493
+ - Pure Rust CFBF / OLE2 container reader
494
+ - Supports v3 (512-byte) and v4 (4096-byte) sectors, mini-streams,
495
+ case-insensitive stream access, path-based lookup
496
+
497
+ - **Legacy module — `crate::doc`** (15 tests)
498
+ - Word Binary (.doc) parser built on `crate::cfb`
499
+ - FIB parsing, piece-table extraction, dual encoding
500
+ (compressed CP1252 + Unicode UTF-16LE)
501
+ - Field-code stripping and special-char sanitization
502
+
503
+ - **Legacy module — `crate::xls`** (24 tests)
504
+ - Excel Binary (.xls) BIFF8 parser built on `crate::cfb`
505
+ - CONTINUE record merging, SST (compressed + wide + rich text),
506
+ RK decode
507
+ - Cell records: LABELSST, NUMBER, RK, MULRK, FORMULA, BOOLERR,
508
+ LABEL, BLANK
509
+
510
+ - **Legacy module — `crate::ppt`** (15 tests)
511
+ - PowerPoint Binary (.ppt) parser built on `crate::cfb`
512
+ - 8-byte record headers, container vs atom records
513
+ - Text extraction from TextCharsAtom (UTF-16LE) and TextBytesAtom
514
+ (Latin-1), SlideListWithText grouping
515
+
516
+ - **Shared core — `crate::core`** (55 tests)
517
+ - `OpcReader` / `OpcWriter` for ZIP-based OPC packages
518
+ - Theme parsing, color resolution, unit types (`Twip`, `HalfPoint`,
519
+ `Emu`)
520
+ - Namespace-aware XML utilities with OOXML Strict support
521
+
522
+ - **Creation API** — write OOXML documents from scratch
523
+ - `crate::docx::write::DocxWriter`, `crate::xlsx::write::XlsxWriter`,
524
+ `crate::pptx::write::PptxWriter`
525
+ - `crate::create::create_from_ir()` and
526
+ `create_from_ir_to_writer()` for IR-to-format conversion
527
+
528
+ - **Editing API** — modify existing documents while preserving unmodified
529
+ parts
530
+ - `crate::docx::edit::EditableDocx`, `crate::xlsx::edit::EditableXlsx`,
531
+ `crate::pptx::edit::EditablePptx`
532
+ - Unified `crate::edit::EditableDocument` with `replace_text()`
533
+ and `set_cell()`
534
+ - `crate::core::editable::EditablePackage` round-trips rels via
535
+ `RelationshipsBuilder::add_with_id()`
536
+
537
+ - **Python bindings** — PyO3 0.28, extension module `office_oxide._native`
538
+ - Type stubs (`_native.pyi`) and PEP 561 `py.typed` marker
539
+ - Wheels for Linux, macOS, Windows across Python 3.8–3.14
540
+
541
+ - **WASM bindings** — `wasm-bindgen`, `WasmDocument` class
542
+ - `new(data, format)`, `plainText()`, `toMarkdown()`, `toHtml()`,
543
+ `toIr()`
544
+ - npm package `office-oxide-wasm`
545
+
546
+ - **CLI** — `office_oxide_cli` workspace crate, binary `office-oxide`
547
+ - Subcommands: `text`, `markdown`, `html`, `info`, `ir`
548
+
549
+ - **MCP server** — `office_oxide_mcp` workspace crate, binary
550
+ `office-oxide-mcp`
551
+ - JSON-RPC 2.0 over stdin/stdout
552
+ - Tools: `extract` (text / markdown / html / ir), `info`
553
+
554
+ - **Feature flags**
555
+ - `python` — PyO3 extension module
556
+ - `wasm` — wasm-bindgen bindings
557
+ - `mmap` — `memmap2`-backed file reading
558
+ - `parallel` — `rayon`-based parallel parsing
559
+
560
+ ### Robustness
561
+
562
+ - OOXML Strict namespace support via dual-namespace matching
563
+ (`strict_alternate()`) and relationship-type normalization
564
+ - Case-insensitive ZIP entry lookup with backslash path normalization
565
+ - Namespace-agnostic attribute lookup (`optional_prefixed_attr_str()`)
566
+ for `d3p1:id` and similar
567
+ - Percent-encoding decoding in `PartName::new()`
568
+ - CRC checksum tolerance in `read_zip_entry()` (accepts data on mismatch)
569
+ - Tolerant numeric parsing: `parse_numeric()` strips unit suffixes,
570
+ handles decimals
571
+ - Shared-string DoS cap: `MAX_CELL_STRING_LEN = 32_768` in `xlsx`
572
+ - Optional parts (`numbering.xml`, theme) degrade gracefully on read
573
+ errors
574
+ - XLSX border aliases: `start`/`end` mapped to `left`/`right` for Strict
575
+ OOXML
576
+
577
+ ### Validation
578
+
579
+ - **98.4% pass rate on a 6,062-file corpus** (5,965 / 6,062) across 11
580
+ open-source test suites (LibreOffice Core, Apache POI, Open XML SDK,
581
+ ClosedXML, Pandoc, python-docx, python-pptx, Apache Tika, calamine,
582
+ openpreserve, oletools)
583
+ - Zero failures on legitimate Word 97+ / Excel 97+ / PowerPoint 97+
584
+ files — all 97 non-passing files are invalid inputs: 43 invalid
585
+ ZIP/CFB archives, 21 missing required parts, 18 malformed XML, and
586
+ 15 non-Office files (WordPerfect, pre-OLE2 Excel 3/4) misnamed with
587
+ Office extensions
588
+ - See [BENCHMARKS.md](BENCHMARKS.md) for per-format timings and the
589
+ full failure breakdown
590
+
591
+ [0.1.1]: https://github.com/yfedoseev/office_oxide/compare/v0.1.0...v0.1.1
592
+ [0.1.0]: https://github.com/yfedoseev/office_oxide/releases/tag/v0.1.0