python-pages 0.1.0__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 (40) hide show
  1. python_pages-0.1.0/LICENSE +21 -0
  2. python_pages-0.1.0/PKG-INFO +271 -0
  3. python_pages-0.1.0/README.md +248 -0
  4. python_pages-0.1.0/pyproject.toml +38 -0
  5. python_pages-0.1.0/setup.cfg +4 -0
  6. python_pages-0.1.0/src/pages/__init__.py +65 -0
  7. python_pages-0.1.0/src/pages/api.py +1869 -0
  8. python_pages-0.1.0/src/pages/cli.py +43 -0
  9. python_pages-0.1.0/src/pages/comments.py +168 -0
  10. python_pages-0.1.0/src/pages/components.py +403 -0
  11. python_pages-0.1.0/src/pages/core_properties.py +126 -0
  12. python_pages-0.1.0/src/pages/data/default.pages +0 -0
  13. python_pages-0.1.0/src/pages/document.py +215 -0
  14. python_pages-0.1.0/src/pages/hyperlinks.py +57 -0
  15. python_pages-0.1.0/src/pages/iwa.py +101 -0
  16. python_pages-0.1.0/src/pages/metadata.py +117 -0
  17. python_pages-0.1.0/src/pages/pages.py +365 -0
  18. python_pages-0.1.0/src/pages/pictures.py +549 -0
  19. python_pages-0.1.0/src/pages/protobuf.py +162 -0
  20. python_pages-0.1.0/src/pages/snappy.py +140 -0
  21. python_pages-0.1.0/src/pages/stylesheet.py +892 -0
  22. python_pages-0.1.0/src/pages/tables.py +493 -0
  23. python_pages-0.1.0/src/pages/text.py +294 -0
  24. python_pages-0.1.0/src/python_pages.egg-info/PKG-INFO +271 -0
  25. python_pages-0.1.0/src/python_pages.egg-info/SOURCES.txt +38 -0
  26. python_pages-0.1.0/src/python_pages.egg-info/dependency_links.txt +1 -0
  27. python_pages-0.1.0/src/python_pages.egg-info/entry_points.txt +2 -0
  28. python_pages-0.1.0/src/python_pages.egg-info/top_level.txt +1 -0
  29. python_pages-0.1.0/tests/test_api.py +157 -0
  30. python_pages-0.1.0/tests/test_comments.py +50 -0
  31. python_pages-0.1.0/tests/test_creation_properties.py +89 -0
  32. python_pages-0.1.0/tests/test_end_to_end.py +100 -0
  33. python_pages-0.1.0/tests/test_hyperlinks_styles.py +70 -0
  34. python_pages-0.1.0/tests/test_paragraph_format.py +142 -0
  35. python_pages-0.1.0/tests/test_pictures.py +64 -0
  36. python_pages-0.1.0/tests/test_python_docx_compat.py +118 -0
  37. python_pages-0.1.0/tests/test_python_docx_ledger.py +27 -0
  38. python_pages-0.1.0/tests/test_sections.py +86 -0
  39. python_pages-0.1.0/tests/test_tables.py +61 -0
  40. python_pages-0.1.0/tests/test_text_editing.py +208 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yuki Tamura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,271 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-pages
3
+ Version: 0.1.0
4
+ Summary: A python-docx-inspired API for Apple Pages documents
5
+ Author-email: Yuki Tamura <isoparametric@isoparametric.xyz>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/isoparametric/python-pages
8
+ Project-URL: Issues, https://github.com/isoparametric/python-pages/issues
9
+ Keywords: apple-pages,document,iwork,python-docx
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Office/Business :: Office Suites
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # python-pages
25
+
26
+ `python-pages` is a Python library for reading and editing modern Apple Pages
27
+ documents with an object model inspired by
28
+ [`python-docx`](https://python-docx.readthedocs.io/). The distribution name is
29
+ `python-pages`; the import name is `pages`:
30
+
31
+ ```python
32
+ from pages import Document
33
+ ```
34
+
35
+ The library edits `.pages` packages directly. It does not launch Pages, use
36
+ AppleScript, or automate the GUI. Its runtime is dependency-free and consists
37
+ of a ZIP container layer, a lossless protobuf-wire layer, an iWork Snappy/IWA
38
+ layer, and a public document API.
39
+
40
+ This project is alpha-quality. Keep the source document and inspect important
41
+ outputs in Pages before using them in production.
42
+
43
+ ## Installation
44
+
45
+ From a local checkout:
46
+
47
+ ```sh
48
+ python3 -m venv .venv
49
+ source .venv/bin/activate
50
+ python3 -m pip install --upgrade pip
51
+ python3 -m pip install -e .
52
+ ```
53
+
54
+ The editable install provides both `import pages` and the `python-pages`
55
+ command. To run without installing, prefix commands with `PYTHONPATH=src`.
56
+
57
+ The project requires Python 3.11 or newer. There are no runtime dependencies.
58
+
59
+ The requested `pages` import name is also used by an unrelated PyPI static-site
60
+ generator. Install `python-pages` in a dedicated virtual environment and do not
61
+ co-install that distribution; both projects would otherwise provide the same
62
+ top-level import name.
63
+
64
+ ## Quick start
65
+
66
+ ```python
67
+ from pages import (
68
+ Document,
69
+ Inches,
70
+ Pt,
71
+ RGBColor,
72
+ WD_ALIGN_PARAGRAPH,
73
+ WD_COLOR_INDEX,
74
+ WD_ORIENT,
75
+ )
76
+
77
+ document = Document("input.pages")
78
+
79
+ paragraph = document.paragraphs[0]
80
+ run = paragraph.runs[0]
81
+ print(paragraph.text, run.text)
82
+
83
+ # Character formatting uses python-docx-style tri-state values.
84
+ run.bold = True
85
+ run.italic = False
86
+ run.underline = None
87
+ run.font.name = "Helvetica-Bold"
88
+ run.font.size = Pt(14)
89
+ run.font.color.rgb = RGBColor(0x22, 0x66, 0xCC)
90
+ run.font.strike = True
91
+ run.font.subscript = False
92
+ run.font.superscript = True
93
+
94
+ # Pages stores the observed text background at paragraph granularity.
95
+ paragraph.highlight_color = WD_COLOR_INDEX.YELLOW
96
+ paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
97
+ paragraph.paragraph_format.line_spacing = Pt(24)
98
+ paragraph.paragraph_format.space_before = Pt(12)
99
+ paragraph.paragraph_format.left_indent = Inches(0.25)
100
+
101
+ paragraph.add_run(" Appended text")
102
+ document.add_paragraph("A new body paragraph")
103
+ document.add_heading("A new heading", level=1)
104
+ document.add_page_break()
105
+
106
+ section = document.sections[0]
107
+ section.orientation = WD_ORIENT.LANDSCAPE
108
+ section.page_width, section.page_height = section.page_height, section.page_width
109
+ section.left_margin = Inches(0.75)
110
+ section.header.paragraphs[0].text = "Header text"
111
+
112
+ document.save("output.pages")
113
+ ```
114
+
115
+ `Document()` without a path starts with a bundled minimal Pages template.
116
+ The template contains blank body text/previews and a neutral white placeholder
117
+ for an otherwise unreachable image graph. The input path is never modified;
118
+ changes are written only by `save()`.
119
+
120
+ ## API coverage
121
+
122
+ The public API intentionally follows python-docx naming where Pages has a
123
+ reasonable counterpart:
124
+
125
+ | Area | python-pages support |
126
+ |---|---|
127
+ | Documents | `Document(path)`, `Document()`, `save()` |
128
+ | Text | `paragraphs`, `Paragraph.text`, `runs`, `Run.text`, insertion and deletion |
129
+ | Fonts | bold, italic, underline, strike, name, size, RGB color, subscript, superscript |
130
+ | Paragraphs | alignment, line spacing, spacing before/after, indents, tab stops, named styles |
131
+ | Sections | one Pages section with page size, margins, orientation, headers, and footers |
132
+ | Tables | read rows/cells, edit materialized text cells, clone a blank table with `add_table()` |
133
+ | Pictures | inspect/replace PNG images and clone an inline image with `add_picture()` |
134
+ | Links and styles | inspect/edit existing hyperlinks; enumerate/apply named paragraph and character styles |
135
+ | Properties | python-docx-shaped `core_properties` facade |
136
+ | Comments | read comments, replies, authors, anchors, and edit existing comment text |
137
+
138
+ Examples for additional object types:
139
+
140
+ ```python
141
+ table = document.tables[0]
142
+ print([[cell.text for cell in row.cells] for row in table.rows])
143
+ table.cell(1, 1).text = "Updated cell"
144
+ new_table = document.add_table(rows=3, cols=4)
145
+
146
+ picture = document.inline_shapes[0]
147
+ print(picture.filename, picture.width, picture.height)
148
+ picture.replace("replacement.png", "replacement-256px-thumbnail.png")
149
+ new_picture = document.add_picture("new-image.png", width=Inches(4))
150
+
151
+ hyperlink = document.hyperlinks[0]
152
+ hyperlink.text = "Project website"
153
+ hyperlink.address = "https://example.com"
154
+
155
+ for comment in document.comments:
156
+ print(comment.author, comment.anchor_text, comment.text)
157
+ print([reply.text for reply in comment.replies])
158
+ ```
159
+
160
+ ## Differences from python-docx
161
+
162
+ `python-pages` is API-inspired, not a byte-format adapter for DOCX. Important
163
+ differences follow from the Pages data model:
164
+
165
+ - Pages page geometry is package-wide, so `Document.sections` contains one
166
+ section. Multiple DOCX sections and linked-to-previous behavior are not
167
+ emulated.
168
+ - A Pages header or footer is exposed as its left, center, and right text
169
+ fragments rather than as a WordprocessingML story part.
170
+ - The observed Pages text-background property belongs to an anonymous
171
+ paragraph style. `Run.font.highlight_color` is retained for call-shape
172
+ compatibility but reads or writes the run's entire containing paragraph.
173
+ - `core_properties` values without a native Pages counterpart are stored in
174
+ `Metadata/PythonPagesCoreProperties.plist`. Documents written by the earlier
175
+ project name remain readable.
176
+ - `add_table()` needs an existing Pages-authored table in the target document
177
+ as a structural template. It supports one tile of up to 256 rows and does
178
+ not implement cell merge or sparse-cell materialization.
179
+ - `add_picture()` needs an existing Pages-authored image graph in the target
180
+ document. New and replacement media are PNG; automatic thumbnails support
181
+ non-interlaced 8-bit RGB/RGBA PNG.
182
+ - Existing comments and replies can be read and edited, but creating a new
183
+ comment is intentionally disabled because Pages also writes collaboration
184
+ and view-state objects whose lifecycle is not yet established.
185
+ - Existing hyperlinks can be edited. Creating a new hyperlink is not yet part
186
+ of the public API.
187
+ - Pages formats are undocumented and can change between application versions.
188
+ The implementation preserves unknown protobuf fields where possible, but it
189
+ cannot guarantee support for every historical or future Pages variant.
190
+
191
+ The detailed compatibility ledger and reverse-engineering evidence are in
192
+ [REPORT.md](REPORT.md).
193
+
194
+ ## Command-line interface
195
+
196
+ ```sh
197
+ python-pages inspect input.pages
198
+ python-pages bold input.pages output.pages "unique body substring"
199
+ ```
200
+
201
+ Without installation:
202
+
203
+ ```sh
204
+ PYTHONPATH=src python3 -m pages.cli inspect input.pages
205
+ ```
206
+
207
+ The `bold` helper expects an unambiguous substring unless `--occurrence` is
208
+ provided. It converts Python string offsets to the UTF-16 offsets used by Pages.
209
+
210
+ ## Optional test fixtures are not distributed
211
+
212
+ The test suite is self-contained and passes from a standalone checkout. Private
213
+ `.pages` files used for Pages.app interoperability checks can contain
214
+ copyrighted or sensitive content, so `samples/` and generated `output/`
215
+ packages are excluded from Git and distributions.
216
+
217
+ To enable the additional real-document tests, place a Pages-authored package at
218
+ `samples/local_fixture.pages`. The tests activate automatically with
219
+ `unittest.skipUnless` when the file is present. It must contain:
220
+
221
+ - at least one inline PNG image;
222
+ - at least one nonempty table;
223
+ - at least one hyperlink; and
224
+ - at least one comment with a reply.
225
+
226
+ No particular headings, formatted runs, or document text are required by these
227
+ optional tests. Use only content you are allowed to publish and do not commit
228
+ the fixture.
229
+
230
+ To enable the python-docx compatibility-ledger audit, place the upstream source
231
+ tree at `ref/python-docx/`. Its tests must be under `ref/python-docx/tests/`;
232
+ the ledger test also activates automatically when that directory is present.
233
+
234
+ Run the suite with:
235
+
236
+ ```sh
237
+ python3 -m unittest discover -s tests -v
238
+ ```
239
+
240
+ Use the decoded-IWA audit tool for every write path:
241
+
242
+ ```sh
243
+ PYTHONPATH=src python3 tools/diff_iwa.py before.pages after.pages
244
+ unzip -t after.pages
245
+ ```
246
+
247
+ ## Architecture
248
+
249
+ - Low level: `snappy.py`, `protobuf.py`, and `iwa.py` provide lossless wire
250
+ parsing and 64-KiB iWork frame handling.
251
+ - Middle level: `pages.py`, `metadata.py`, `stylesheet.py`, `text.py`, and the
252
+ component-specific modules traverse and mutate Pages object graphs.
253
+ - High level: `api.py` provides the python-docx-shaped object model exported by
254
+ `pages`.
255
+
256
+ Synthesized styles are registered in the stylesheet, parent/child map,
257
+ MessageInfo references, PackageMetadata UUID map, and cross-component reference
258
+ map. Untouched ZIP members and IWA payloads are preserved byte-for-byte; changed
259
+ members are re-encoded into valid Pages packages.
260
+
261
+ ## Project status
262
+
263
+ The compatibility ledger accounts for all 59 python-docx test files and 662
264
+ discovered behavior methods as migrated, partially migrated, or format-specific
265
+ exclusions. When `ref/python-docx/` is present, the optional ledger test verifies
266
+ that the upstream checkout contains behavior tests. The dependency-free test
267
+ suite runs standalone; optional fixture and ledger tests activate automatically
268
+ when their local inputs are present.
269
+
270
+ `python-pages` is an independent project and is not affiliated with Apple or
271
+ the python-docx project.
@@ -0,0 +1,248 @@
1
+ # python-pages
2
+
3
+ `python-pages` is a Python library for reading and editing modern Apple Pages
4
+ documents with an object model inspired by
5
+ [`python-docx`](https://python-docx.readthedocs.io/). The distribution name is
6
+ `python-pages`; the import name is `pages`:
7
+
8
+ ```python
9
+ from pages import Document
10
+ ```
11
+
12
+ The library edits `.pages` packages directly. It does not launch Pages, use
13
+ AppleScript, or automate the GUI. Its runtime is dependency-free and consists
14
+ of a ZIP container layer, a lossless protobuf-wire layer, an iWork Snappy/IWA
15
+ layer, and a public document API.
16
+
17
+ This project is alpha-quality. Keep the source document and inspect important
18
+ outputs in Pages before using them in production.
19
+
20
+ ## Installation
21
+
22
+ From a local checkout:
23
+
24
+ ```sh
25
+ python3 -m venv .venv
26
+ source .venv/bin/activate
27
+ python3 -m pip install --upgrade pip
28
+ python3 -m pip install -e .
29
+ ```
30
+
31
+ The editable install provides both `import pages` and the `python-pages`
32
+ command. To run without installing, prefix commands with `PYTHONPATH=src`.
33
+
34
+ The project requires Python 3.11 or newer. There are no runtime dependencies.
35
+
36
+ The requested `pages` import name is also used by an unrelated PyPI static-site
37
+ generator. Install `python-pages` in a dedicated virtual environment and do not
38
+ co-install that distribution; both projects would otherwise provide the same
39
+ top-level import name.
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ from pages import (
45
+ Document,
46
+ Inches,
47
+ Pt,
48
+ RGBColor,
49
+ WD_ALIGN_PARAGRAPH,
50
+ WD_COLOR_INDEX,
51
+ WD_ORIENT,
52
+ )
53
+
54
+ document = Document("input.pages")
55
+
56
+ paragraph = document.paragraphs[0]
57
+ run = paragraph.runs[0]
58
+ print(paragraph.text, run.text)
59
+
60
+ # Character formatting uses python-docx-style tri-state values.
61
+ run.bold = True
62
+ run.italic = False
63
+ run.underline = None
64
+ run.font.name = "Helvetica-Bold"
65
+ run.font.size = Pt(14)
66
+ run.font.color.rgb = RGBColor(0x22, 0x66, 0xCC)
67
+ run.font.strike = True
68
+ run.font.subscript = False
69
+ run.font.superscript = True
70
+
71
+ # Pages stores the observed text background at paragraph granularity.
72
+ paragraph.highlight_color = WD_COLOR_INDEX.YELLOW
73
+ paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
74
+ paragraph.paragraph_format.line_spacing = Pt(24)
75
+ paragraph.paragraph_format.space_before = Pt(12)
76
+ paragraph.paragraph_format.left_indent = Inches(0.25)
77
+
78
+ paragraph.add_run(" Appended text")
79
+ document.add_paragraph("A new body paragraph")
80
+ document.add_heading("A new heading", level=1)
81
+ document.add_page_break()
82
+
83
+ section = document.sections[0]
84
+ section.orientation = WD_ORIENT.LANDSCAPE
85
+ section.page_width, section.page_height = section.page_height, section.page_width
86
+ section.left_margin = Inches(0.75)
87
+ section.header.paragraphs[0].text = "Header text"
88
+
89
+ document.save("output.pages")
90
+ ```
91
+
92
+ `Document()` without a path starts with a bundled minimal Pages template.
93
+ The template contains blank body text/previews and a neutral white placeholder
94
+ for an otherwise unreachable image graph. The input path is never modified;
95
+ changes are written only by `save()`.
96
+
97
+ ## API coverage
98
+
99
+ The public API intentionally follows python-docx naming where Pages has a
100
+ reasonable counterpart:
101
+
102
+ | Area | python-pages support |
103
+ |---|---|
104
+ | Documents | `Document(path)`, `Document()`, `save()` |
105
+ | Text | `paragraphs`, `Paragraph.text`, `runs`, `Run.text`, insertion and deletion |
106
+ | Fonts | bold, italic, underline, strike, name, size, RGB color, subscript, superscript |
107
+ | Paragraphs | alignment, line spacing, spacing before/after, indents, tab stops, named styles |
108
+ | Sections | one Pages section with page size, margins, orientation, headers, and footers |
109
+ | Tables | read rows/cells, edit materialized text cells, clone a blank table with `add_table()` |
110
+ | Pictures | inspect/replace PNG images and clone an inline image with `add_picture()` |
111
+ | Links and styles | inspect/edit existing hyperlinks; enumerate/apply named paragraph and character styles |
112
+ | Properties | python-docx-shaped `core_properties` facade |
113
+ | Comments | read comments, replies, authors, anchors, and edit existing comment text |
114
+
115
+ Examples for additional object types:
116
+
117
+ ```python
118
+ table = document.tables[0]
119
+ print([[cell.text for cell in row.cells] for row in table.rows])
120
+ table.cell(1, 1).text = "Updated cell"
121
+ new_table = document.add_table(rows=3, cols=4)
122
+
123
+ picture = document.inline_shapes[0]
124
+ print(picture.filename, picture.width, picture.height)
125
+ picture.replace("replacement.png", "replacement-256px-thumbnail.png")
126
+ new_picture = document.add_picture("new-image.png", width=Inches(4))
127
+
128
+ hyperlink = document.hyperlinks[0]
129
+ hyperlink.text = "Project website"
130
+ hyperlink.address = "https://example.com"
131
+
132
+ for comment in document.comments:
133
+ print(comment.author, comment.anchor_text, comment.text)
134
+ print([reply.text for reply in comment.replies])
135
+ ```
136
+
137
+ ## Differences from python-docx
138
+
139
+ `python-pages` is API-inspired, not a byte-format adapter for DOCX. Important
140
+ differences follow from the Pages data model:
141
+
142
+ - Pages page geometry is package-wide, so `Document.sections` contains one
143
+ section. Multiple DOCX sections and linked-to-previous behavior are not
144
+ emulated.
145
+ - A Pages header or footer is exposed as its left, center, and right text
146
+ fragments rather than as a WordprocessingML story part.
147
+ - The observed Pages text-background property belongs to an anonymous
148
+ paragraph style. `Run.font.highlight_color` is retained for call-shape
149
+ compatibility but reads or writes the run's entire containing paragraph.
150
+ - `core_properties` values without a native Pages counterpart are stored in
151
+ `Metadata/PythonPagesCoreProperties.plist`. Documents written by the earlier
152
+ project name remain readable.
153
+ - `add_table()` needs an existing Pages-authored table in the target document
154
+ as a structural template. It supports one tile of up to 256 rows and does
155
+ not implement cell merge or sparse-cell materialization.
156
+ - `add_picture()` needs an existing Pages-authored image graph in the target
157
+ document. New and replacement media are PNG; automatic thumbnails support
158
+ non-interlaced 8-bit RGB/RGBA PNG.
159
+ - Existing comments and replies can be read and edited, but creating a new
160
+ comment is intentionally disabled because Pages also writes collaboration
161
+ and view-state objects whose lifecycle is not yet established.
162
+ - Existing hyperlinks can be edited. Creating a new hyperlink is not yet part
163
+ of the public API.
164
+ - Pages formats are undocumented and can change between application versions.
165
+ The implementation preserves unknown protobuf fields where possible, but it
166
+ cannot guarantee support for every historical or future Pages variant.
167
+
168
+ The detailed compatibility ledger and reverse-engineering evidence are in
169
+ [REPORT.md](REPORT.md).
170
+
171
+ ## Command-line interface
172
+
173
+ ```sh
174
+ python-pages inspect input.pages
175
+ python-pages bold input.pages output.pages "unique body substring"
176
+ ```
177
+
178
+ Without installation:
179
+
180
+ ```sh
181
+ PYTHONPATH=src python3 -m pages.cli inspect input.pages
182
+ ```
183
+
184
+ The `bold` helper expects an unambiguous substring unless `--occurrence` is
185
+ provided. It converts Python string offsets to the UTF-16 offsets used by Pages.
186
+
187
+ ## Optional test fixtures are not distributed
188
+
189
+ The test suite is self-contained and passes from a standalone checkout. Private
190
+ `.pages` files used for Pages.app interoperability checks can contain
191
+ copyrighted or sensitive content, so `samples/` and generated `output/`
192
+ packages are excluded from Git and distributions.
193
+
194
+ To enable the additional real-document tests, place a Pages-authored package at
195
+ `samples/local_fixture.pages`. The tests activate automatically with
196
+ `unittest.skipUnless` when the file is present. It must contain:
197
+
198
+ - at least one inline PNG image;
199
+ - at least one nonempty table;
200
+ - at least one hyperlink; and
201
+ - at least one comment with a reply.
202
+
203
+ No particular headings, formatted runs, or document text are required by these
204
+ optional tests. Use only content you are allowed to publish and do not commit
205
+ the fixture.
206
+
207
+ To enable the python-docx compatibility-ledger audit, place the upstream source
208
+ tree at `ref/python-docx/`. Its tests must be under `ref/python-docx/tests/`;
209
+ the ledger test also activates automatically when that directory is present.
210
+
211
+ Run the suite with:
212
+
213
+ ```sh
214
+ python3 -m unittest discover -s tests -v
215
+ ```
216
+
217
+ Use the decoded-IWA audit tool for every write path:
218
+
219
+ ```sh
220
+ PYTHONPATH=src python3 tools/diff_iwa.py before.pages after.pages
221
+ unzip -t after.pages
222
+ ```
223
+
224
+ ## Architecture
225
+
226
+ - Low level: `snappy.py`, `protobuf.py`, and `iwa.py` provide lossless wire
227
+ parsing and 64-KiB iWork frame handling.
228
+ - Middle level: `pages.py`, `metadata.py`, `stylesheet.py`, `text.py`, and the
229
+ component-specific modules traverse and mutate Pages object graphs.
230
+ - High level: `api.py` provides the python-docx-shaped object model exported by
231
+ `pages`.
232
+
233
+ Synthesized styles are registered in the stylesheet, parent/child map,
234
+ MessageInfo references, PackageMetadata UUID map, and cross-component reference
235
+ map. Untouched ZIP members and IWA payloads are preserved byte-for-byte; changed
236
+ members are re-encoded into valid Pages packages.
237
+
238
+ ## Project status
239
+
240
+ The compatibility ledger accounts for all 59 python-docx test files and 662
241
+ discovered behavior methods as migrated, partially migrated, or format-specific
242
+ exclusions. When `ref/python-docx/` is present, the optional ledger test verifies
243
+ that the upstream checkout contains behavior tests. The dependency-free test
244
+ suite runs standalone; optional fixture and ledger tests activate automatically
245
+ when their local inputs are present.
246
+
247
+ `python-pages` is an independent project and is not affiliated with Apple or
248
+ the python-docx project.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python-pages"
7
+ version = "0.1.0"
8
+ description = "A python-docx-inspired API for Apple Pages documents"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Yuki Tamura", email = "isoparametric@isoparametric.xyz" }]
13
+ requires-python = ">=3.11"
14
+ keywords = ["apple-pages", "document", "iwork", "python-docx"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Office/Business :: Office Suites",
25
+ ]
26
+
27
+ [project.urls]
28
+ Repository = "https://github.com/isoparametric/python-pages"
29
+ Issues = "https://github.com/isoparametric/python-pages/issues"
30
+
31
+ [project.scripts]
32
+ python-pages = "pages.cli:main"
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["src"]
36
+
37
+ [tool.setuptools.package-data]
38
+ pages = ["data/*.pages"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,65 @@
1
+ """Dependency-free Apple Pages IWA reading and character formatting."""
2
+
3
+ from .api import (
4
+ AnnotationHighlight,
5
+ Cm,
6
+ Comment,
7
+ Comments,
8
+ Document,
9
+ Inches,
10
+ InlineShape,
11
+ Hyperlink,
12
+ Style,
13
+ Styles,
14
+ Length,
15
+ Pt,
16
+ Section,
17
+ Table,
18
+ TableCell,
19
+ WD_ALIGN_PARAGRAPH,
20
+ WD_COLOR,
21
+ WD_COLOR_INDEX,
22
+ WD_LINE_SPACING,
23
+ WD_PARAGRAPH_ALIGNMENT,
24
+ WD_TAB_ALIGNMENT,
25
+ WD_TAB_LEADER,
26
+ WD_ORIENT,
27
+ WD_ORIENTATION,
28
+ WD_STYLE_TYPE,
29
+ )
30
+ from .pages import PagesDocument
31
+ from .core_properties import CoreProperties
32
+ from .stylesheet import CharacterProperties, ParagraphProperties, RGBColor
33
+
34
+ __all__ = [
35
+ "AnnotationHighlight",
36
+ "CharacterProperties",
37
+ "Cm",
38
+ "Comment",
39
+ "Comments",
40
+ "CoreProperties",
41
+ "Document",
42
+ "Inches",
43
+ "InlineShape",
44
+ "Hyperlink",
45
+ "Style",
46
+ "Styles",
47
+ "Length",
48
+ "PagesDocument",
49
+ "ParagraphProperties",
50
+ "Pt",
51
+ "RGBColor",
52
+ "Section",
53
+ "Table",
54
+ "TableCell",
55
+ "WD_ALIGN_PARAGRAPH",
56
+ "WD_COLOR",
57
+ "WD_COLOR_INDEX",
58
+ "WD_LINE_SPACING",
59
+ "WD_PARAGRAPH_ALIGNMENT",
60
+ "WD_TAB_ALIGNMENT",
61
+ "WD_TAB_LEADER",
62
+ "WD_ORIENT",
63
+ "WD_ORIENTATION",
64
+ "WD_STYLE_TYPE",
65
+ ]