spotpdf 0.7.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 (144) hide show
  1. spotpdf-0.7.0/CHANGELOG.md +287 -0
  2. spotpdf-0.7.0/CONTRIBUTING.md +195 -0
  3. spotpdf-0.7.0/LICENSE +21 -0
  4. spotpdf-0.7.0/MANIFEST.in +32 -0
  5. spotpdf-0.7.0/PKG-INFO +525 -0
  6. spotpdf-0.7.0/README.md +495 -0
  7. spotpdf-0.7.0/SECURITY.md +67 -0
  8. spotpdf-0.7.0/SUPPORT.md +57 -0
  9. spotpdf-0.7.0/corpus/manifest.toml +104 -0
  10. spotpdf-0.7.0/docs/architecture.md +265 -0
  11. spotpdf-0.7.0/docs/compatibility.md +386 -0
  12. spotpdf-0.7.0/docs/images/demo-after.png +0 -0
  13. spotpdf-0.7.0/docs/images/demo-alternate.png +0 -0
  14. spotpdf-0.7.0/docs/images/demo-before.png +0 -0
  15. spotpdf-0.7.0/docs/images/demo-convert.svg +26 -0
  16. spotpdf-0.7.0/docs/images/demo-rename.svg +27 -0
  17. spotpdf-0.7.0/docs/images/generation.json +63 -0
  18. spotpdf-0.7.0/docs/json-output.md +346 -0
  19. spotpdf-0.7.0/docs/performance.md +76 -0
  20. spotpdf-0.7.0/docs/processing-budgets.md +155 -0
  21. spotpdf-0.7.0/docs/public-corpus.md +63 -0
  22. spotpdf-0.7.0/docs/python-api.md +152 -0
  23. spotpdf-0.7.0/docs/releasing.md +201 -0
  24. spotpdf-0.7.0/examples/create_demo_pdf.py +137 -0
  25. spotpdf-0.7.0/pyproject.toml +56 -0
  26. spotpdf-0.7.0/scripts/benchmark_inventory.py +240 -0
  27. spotpdf-0.7.0/scripts/check_distribution.py +255 -0
  28. spotpdf-0.7.0/scripts/check_public_corpus.py +40 -0
  29. spotpdf-0.7.0/scripts/check_pypi_readme.py +366 -0
  30. spotpdf-0.7.0/scripts/check_python_size.py +26 -0
  31. spotpdf-0.7.0/scripts/check_release.py +567 -0
  32. spotpdf-0.7.0/scripts/check_repository.py +334 -0
  33. spotpdf-0.7.0/scripts/create_docs_images.py +570 -0
  34. spotpdf-0.7.0/scripts/docs_image_check.py +229 -0
  35. spotpdf-0.7.0/scripts/public_corpus.py +460 -0
  36. spotpdf-0.7.0/scripts/release_readme.py +260 -0
  37. spotpdf-0.7.0/scripts/smoke_distributions.py +299 -0
  38. spotpdf-0.7.0/setup.cfg +4 -0
  39. spotpdf-0.7.0/src/spotpdf/__init__.py +54 -0
  40. spotpdf-0.7.0/src/spotpdf/__main__.py +3 -0
  41. spotpdf-0.7.0/src/spotpdf/alternate.py +146 -0
  42. spotpdf-0.7.0/src/spotpdf/alternate_plan.py +286 -0
  43. spotpdf-0.7.0/src/spotpdf/alternate_validation.py +272 -0
  44. spotpdf-0.7.0/src/spotpdf/budget_content.py +123 -0
  45. spotpdf-0.7.0/src/spotpdf/budget_graph.py +83 -0
  46. spotpdf-0.7.0/src/spotpdf/budget_preflight.py +60 -0
  47. spotpdf-0.7.0/src/spotpdf/cli.py +128 -0
  48. spotpdf-0.7.0/src/spotpdf/cli_dry_run.py +31 -0
  49. spotpdf-0.7.0/src/spotpdf/cli_limits.py +90 -0
  50. spotpdf-0.7.0/src/spotpdf/cli_output.py +477 -0
  51. spotpdf-0.7.0/src/spotpdf/cli_parser.py +254 -0
  52. spotpdf-0.7.0/src/spotpdf/cmyk.py +144 -0
  53. spotpdf-0.7.0/src/spotpdf/colors.py +148 -0
  54. spotpdf-0.7.0/src/spotpdf/content.py +401 -0
  55. spotpdf-0.7.0/src/spotpdf/content_support.py +105 -0
  56. spotpdf-0.7.0/src/spotpdf/convert.py +143 -0
  57. spotpdf-0.7.0/src/spotpdf/convert_aliases.py +303 -0
  58. spotpdf-0.7.0/src/spotpdf/convert_content.py +583 -0
  59. spotpdf-0.7.0/src/spotpdf/convert_operators.py +67 -0
  60. spotpdf-0.7.0/src/spotpdf/convert_plan.py +128 -0
  61. spotpdf-0.7.0/src/spotpdf/convert_resource_contexts.py +307 -0
  62. spotpdf-0.7.0/src/spotpdf/convert_resources.py +121 -0
  63. spotpdf-0.7.0/src/spotpdf/convert_state.py +64 -0
  64. spotpdf-0.7.0/src/spotpdf/convert_stream_owners.py +243 -0
  65. spotpdf-0.7.0/src/spotpdf/convert_streams.py +426 -0
  66. spotpdf-0.7.0/src/spotpdf/document.py +543 -0
  67. spotpdf-0.7.0/src/spotpdf/inspection.py +44 -0
  68. spotpdf-0.7.0/src/spotpdf/inventory.py +527 -0
  69. spotpdf-0.7.0/src/spotpdf/inventory_content.py +600 -0
  70. spotpdf-0.7.0/src/spotpdf/inventory_graph.py +159 -0
  71. spotpdf-0.7.0/src/spotpdf/inventory_hazards.py +359 -0
  72. spotpdf-0.7.0/src/spotpdf/inventory_prepress.py +168 -0
  73. spotpdf-0.7.0/src/spotpdf/inventory_usage.py +65 -0
  74. spotpdf-0.7.0/src/spotpdf/inventory_values.py +128 -0
  75. spotpdf-0.7.0/src/spotpdf/limits.py +103 -0
  76. spotpdf-0.7.0/src/spotpdf/metadata_fingerprint.py +165 -0
  77. spotpdf-0.7.0/src/spotpdf/model.py +264 -0
  78. spotpdf-0.7.0/src/spotpdf/mutation_verification.py +139 -0
  79. spotpdf-0.7.0/src/spotpdf/objects.py +47 -0
  80. spotpdf-0.7.0/src/spotpdf/publication.py +206 -0
  81. spotpdf-0.7.0/src/spotpdf/py.typed +0 -0
  82. spotpdf-0.7.0/src/spotpdf/removal_resources.py +228 -0
  83. spotpdf-0.7.0/src/spotpdf/rename.py +236 -0
  84. spotpdf-0.7.0/src/spotpdf/rename_hazards.py +261 -0
  85. spotpdf-0.7.0/src/spotpdf/rename_plan.py +595 -0
  86. spotpdf-0.7.0/src/spotpdf/rename_request.py +74 -0
  87. spotpdf-0.7.0/src/spotpdf/rename_slots.py +412 -0
  88. spotpdf-0.7.0/src/spotpdf/rename_structures.py +320 -0
  89. spotpdf-0.7.0/src/spotpdf/resource_owners.py +62 -0
  90. spotpdf-0.7.0/src/spotpdf/scan.py +324 -0
  91. spotpdf-0.7.0/src/spotpdf/separation_targets.py +233 -0
  92. spotpdf-0.7.0/src/spotpdf/trailer_semantics.py +30 -0
  93. spotpdf-0.7.0/src/spotpdf.egg-info/PKG-INFO +525 -0
  94. spotpdf-0.7.0/src/spotpdf.egg-info/SOURCES.txt +142 -0
  95. spotpdf-0.7.0/src/spotpdf.egg-info/dependency_links.txt +1 -0
  96. spotpdf-0.7.0/src/spotpdf.egg-info/entry_points.txt +2 -0
  97. spotpdf-0.7.0/src/spotpdf.egg-info/requires.txt +1 -0
  98. spotpdf-0.7.0/src/spotpdf.egg-info/top_level.txt +1 -0
  99. spotpdf-0.7.0/tests/cli_json_helpers.py +215 -0
  100. spotpdf-0.7.0/tests/conversion_fixtures.py +66 -0
  101. spotpdf-0.7.0/tests/processing_limit_fixtures.py +96 -0
  102. spotpdf-0.7.0/tests/test_alternate_safety.py +308 -0
  103. spotpdf-0.7.0/tests/test_cli_dry_run.py +487 -0
  104. spotpdf-0.7.0/tests/test_cli_json.py +297 -0
  105. spotpdf-0.7.0/tests/test_cli_json_errors.py +350 -0
  106. spotpdf-0.7.0/tests/test_contributor_docs.py +54 -0
  107. spotpdf-0.7.0/tests/test_convert.py +224 -0
  108. spotpdf-0.7.0/tests/test_convert_alias_dependencies.py +162 -0
  109. spotpdf-0.7.0/tests/test_convert_content.py +267 -0
  110. spotpdf-0.7.0/tests/test_convert_content_matrix.py +277 -0
  111. spotpdf-0.7.0/tests/test_convert_forms.py +263 -0
  112. spotpdf-0.7.0/tests/test_convert_render.py +136 -0
  113. spotpdf-0.7.0/tests/test_convert_resource_contexts.py +174 -0
  114. spotpdf-0.7.0/tests/test_convert_safety.py +573 -0
  115. spotpdf-0.7.0/tests/test_convert_stream_owners.py +424 -0
  116. spotpdf-0.7.0/tests/test_convert_verification.py +83 -0
  117. spotpdf-0.7.0/tests/test_demo.py +32 -0
  118. spotpdf-0.7.0/tests/test_distribution_check.py +225 -0
  119. spotpdf-0.7.0/tests/test_docs_image_check.py +262 -0
  120. spotpdf-0.7.0/tests/test_inventory.py +438 -0
  121. spotpdf-0.7.0/tests/test_inventory_edges.py +293 -0
  122. spotpdf-0.7.0/tests/test_inventory_performance.py +148 -0
  123. spotpdf-0.7.0/tests/test_inventory_usage.py +565 -0
  124. spotpdf-0.7.0/tests/test_metadata_fingerprint.py +157 -0
  125. spotpdf-0.7.0/tests/test_processing_limits.py +599 -0
  126. spotpdf-0.7.0/tests/test_public_api.py +394 -0
  127. spotpdf-0.7.0/tests/test_public_corpus.py +38 -0
  128. spotpdf-0.7.0/tests/test_pypi_readme.py +267 -0
  129. spotpdf-0.7.0/tests/test_release_checks.py +551 -0
  130. spotpdf-0.7.0/tests/test_release_readme.py +175 -0
  131. spotpdf-0.7.0/tests/test_release_workflow.py +119 -0
  132. spotpdf-0.7.0/tests/test_removal_resource_contexts.py +494 -0
  133. spotpdf-0.7.0/tests/test_rename.py +591 -0
  134. spotpdf-0.7.0/tests/test_rename_malformed_edges.py +243 -0
  135. spotpdf-0.7.0/tests/test_rename_render.py +169 -0
  136. spotpdf-0.7.0/tests/test_rename_safety.py +420 -0
  137. spotpdf-0.7.0/tests/test_rename_semantics.py +560 -0
  138. spotpdf-0.7.0/tests/test_rename_structures.py +259 -0
  139. spotpdf-0.7.0/tests/test_rename_verification.py +286 -0
  140. spotpdf-0.7.0/tests/test_repository_checks.py +278 -0
  141. spotpdf-0.7.0/tests/test_safety.py +148 -0
  142. spotpdf-0.7.0/tests/test_set_alternate.py +457 -0
  143. spotpdf-0.7.0/tests/test_spotpdf.py +499 -0
  144. spotpdf-0.7.0/tests/test_troubleshooting_docs.py +129 -0
@@ -0,0 +1,287 @@
1
+ # Changelog
2
+
3
+ All notable user-visible changes are documented here. The project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.7.0] - 2026-08-30
9
+
10
+ ### Added
11
+
12
+ - Automated repository hygiene checks reject tracked PDFs and broken local
13
+ documentation file targets, while CI regenerates every synthetic documentation
14
+ image, verifies an exact source-fingerprint manifest and SVG output, and uses
15
+ bounded PNG drift checks before packaging.
16
+ - CodeQL analysis for Python runs on pull requests, `main`, a weekly schedule,
17
+ and manual dispatch with read-only contents plus the required code-scanning
18
+ write permission.
19
+ - A support-routing guide and structured usage-question form distinguish
20
+ questions, reproducible bugs, feature requests, and private vulnerability
21
+ reports while keeping confidential PDFs and sensitive output data out of
22
+ project channels.
23
+ - A tokenless PyPI Trusted Publishing path uploads the already tested wheel and
24
+ source archive only after the corresponding GitHub Release is immutable.
25
+ - Release validation now rejects repository-relative or stale-tag README links
26
+ that would break on PyPI. A fail-closed PyPI Markdown gate renders the exact
27
+ long description packaged in both distributions and requires them to match;
28
+ bounded archive parsing rejects resource floods and ambiguous metadata paths.
29
+ `twine check --strict` remains responsible for metadata and warning checks.
30
+ - Every mutating CLI command now accepts `--dry-run` instead of an output path.
31
+ The mode executes the complete rewrite, serialization, strict reopen, and
32
+ semantic verification pipeline, then discards the verified temporary PDF.
33
+ Text identifies the dry run; JSON adds `dry_run: true` and omits `output`
34
+ without changing normal mutation result shapes.
35
+ - Wheel and source-archive Core Metadata now expose the canonical Support and
36
+ Security channels. The package gate requires each link exactly once in both
37
+ distributions before publication.
38
+ - Contributor setup now names Poppler's `pdftoppm` prerequisite, provides
39
+ macOS and Debian/Ubuntu installation commands, adds POSIX and PowerShell
40
+ verification, and distinguishes Poppler-only documentation/rename rendering
41
+ from qpdf/Ghostscript convert-render and release-corpus requirements.
42
+ - An actionable troubleshooting guide maps common fail-closed errors to causes
43
+ and safe next steps without treating `--force`, higher budgets, or PDF-object
44
+ patching as ways to bypass unsupported semantics and document restrictions.
45
+ - The seven supported Python operations, their core result records, limits,
46
+ colorant roles/kinds, and controlled errors now have one canonical package-root
47
+ import surface. All path arguments consistently accept strings and path-like
48
+ objects, and built distributions declare and verify PEP 561 inline typing.
49
+ - Stable-release validation now binds both the bug-report and usage-question
50
+ version placeholders to the package version.
51
+
52
+ ### Security
53
+
54
+ - Spot removal now plans every target resource-alias deletion only inside a
55
+ proven Page/Form content-resource context. Genuine but uninvoked Forms are
56
+ analyzed across every effective resource scope before selected aliases can be
57
+ removed. Retained color-space dependencies, Form inline images, selected
58
+ targets reachable through non-content trailer roots, private resource
59
+ lookalikes, and resource containers with non-content owners fail closed
60
+ without replacing an existing destination.
61
+ - The PyPI publisher is isolated in a two-step, tag-only environment job with
62
+ only `id-token: write`; it cannot check out or rebuild repository code and
63
+ uses no stored package-index credential.
64
+ - Read-only CI checkouts no longer retain Git credentials beyond checkout.
65
+ - Dry-run success is emitted only after private temporary storage has been
66
+ cleaned up. Handled mutation failures remove the private output; a filesystem
67
+ cleanup failure becomes an I/O error before any success record is emitted.
68
+
69
+ ## [0.6.0] - 2026-08-30
70
+
71
+ ### Added
72
+
73
+ - Schema-versioned JSON output for all six input-processing commands through
74
+ `--format json`, with one deterministic command-specific result on stdout or
75
+ one classified error on stderr.
76
+ - Stable machine error codes for usage, budgets, unsupported spot semantics,
77
+ validation, PDF parsing, I/O, invalid values, invariant failures, and nesting
78
+ limits. Budget failures include the metric, public field, observed value,
79
+ limit, and matching CLI option.
80
+ - A normative JSON contract with Enfocus Switch, shell, and CI recipes plus
81
+ subprocess coverage for every command, empty results, Unicode and controls,
82
+ parser failures, native decode failures, and output atomicity.
83
+
84
+ ### Changed
85
+
86
+ - Invalid command-line arguments now exit with `64`, leaving exit `2`
87
+ exclusively for a successful `check` result whose requested name is present.
88
+ - Stable-release validation now also rejects stale development-only claims and
89
+ mismatched `spotpdf_version` examples in the README and normative JSON,
90
+ processing-budget, and security documentation.
91
+
92
+ ### Compatibility
93
+
94
+ - Human-readable text remains the default; successful command output and
95
+ runtime-error wording remain unchanged. Help now documents `--format`, usage
96
+ errors move from exit `2` to `64`, successful mutations remain exit `0`, and
97
+ `check` remains exit `0` for absent and `2` for present. Help and version
98
+ output remain text in JSON mode.
99
+
100
+ ## [0.5.0] - 2026-08-30
101
+
102
+ spotpdf 0.5.0 adds fail-closed conversion of one supported named
103
+ `/Separation` to explicit DeviceCMYK vector or text paint. The operator supplies
104
+ the CMYK recipe; spotpdf never guesses it from the existing alternate preview.
105
+
106
+ ```console
107
+ spotpdf convert input.pdf --spot "Varnish" \
108
+ --to-cmyk 0,80,100,0 -o output.pdf
109
+ ```
110
+
111
+ ### Added
112
+
113
+ - `convert` CLI command and `convert_spot_to_cmyk` library API for replacing a
114
+ strictly supported named Separation with explicit DeviceCMYK vector/text
115
+ paint from an operator-supplied process recipe.
116
+ - Stateful tint conversion across independent fill/stroke state, balanced
117
+ graphics-state scopes, text rendering modes, and compatible nested Forms.
118
+ - A synthetic qpdf/Poppler/Ghostscript conversion oracle, generated conversion
119
+ walkthrough, and seventh pinned public-corpus case.
120
+ - Immutable public `ProcessingLimits` configuration and five positive-integer
121
+ CLI overrides for input bytes, pages, reachable graph entries, decoded
122
+ page/Form content bytes, and content operators.
123
+ - A normative processing-budget guide with exact inclusive counter semantics,
124
+ library examples, default rationale, and isolation boundaries.
125
+
126
+ ### Safety
127
+
128
+ - Conversion builds and verifies a complete precomputed resource/stream plan,
129
+ requires the target plate to be absent after strict reopen, and fingerprints
130
+ every unplanned document semantic before atomic publication.
131
+ - A resource-scope-aware preflight proves that deleting target aliases cannot
132
+ leave nested color spaces, images, shadings, transparency groups, or Tiling
133
+ Pattern content with stale references.
134
+ - Explicit process conversion fails closed for target-related DeviceN,
135
+ Type 5 halftones, OPI and other prepress dependencies, images and their
136
+ alternates, patterns, shadings, Type 3 fonts, annotation appearances,
137
+ transparency, effective overprint, effective `/DefaultCMYK`, ambiguous
138
+ resource/Form contexts, and malformed or unsupported paint state.
139
+ - Unknown content operators outside PDF `BX`/`EX` compatibility sections are
140
+ rejected before conversion so vendor extensions cannot hide paint or graphics
141
+ state changes from the conversion plan. Standard operators are also checked
142
+ against page/Form versus text-object context, and ExtGState font selections
143
+ participate in the Type 3 refusal.
144
+ - A stream-owner preflight refuses page/Form cross-role aliases, non-content
145
+ aliases such as attachments and metadata, and externally referenced
146
+ `/Contents` Array members. Only direct `/XObject` slots in proven Page/Form
147
+ resource contexts authorize Form writes; external StructTree MCR `/Stm`
148
+ associations and private lookalike structures fail closed.
149
+ - Every input-processing subcommand now performs one fixed-order source budget
150
+ preflight before analysis or mutation. Any overrun publishes no output,
151
+ preserves an existing `--force` destination, and leaves no created private
152
+ temporary candidate behind.
153
+ - Reachable graph entries and content operators are counted incrementally rather
154
+ than materializing complete graph-edge or instruction lists solely for the
155
+ budget check. Application ceilings remain separate from qpdf safeguards and
156
+ external CPU/RAM/time isolation.
157
+ - Native qpdf warnings remain collected and fail closed but are no longer
158
+ duplicated ahead of the CLI's single user-facing error line.
159
+
160
+ ## [0.4.0] - 2026-08-30
161
+
162
+ ### Added
163
+
164
+ - `set-alternate` command for replacing every matching Separation's composite
165
+ fallback with one linear DeviceCMYK FunctionType 2 preview while preserving
166
+ the spot plate and all paint operands.
167
+ - A real veraPDF public-corpus case and synthetic before/after render for
168
+ alternate-preview changes.
169
+ - A reproducible synthetic 64/128-spot inventory benchmark with deterministic
170
+ page/Form parse counts, timings, and Python heap measurements.
171
+
172
+ ### Changed
173
+
174
+ - Read-only inventory now attributes structural hazards in one traversal and
175
+ interprets every reached page or compatible Form stream once, eliminating the
176
+ previous colorant-by-stream parse multiplier while preserving per-colorant
177
+ status and paint counters.
178
+
179
+ ### Fixed
180
+
181
+ - Shared Forms with direct resource dictionaries now use stable owner-bound
182
+ identities instead of transient pikepdf wrapper IDs, and compatible shared
183
+ Form use is attributed to every calling page without recounting its paint.
184
+ - Cached changes from nested shared Forms now propagate through every enclosing
185
+ Form, so removal reports every affected calling page even when the inner Form
186
+ was first reached through another path.
187
+ - Inline images are recognized with current pikepdf objects. Page aliases and
188
+ Form streams that would require rewriting now fail during the dry run rather
189
+ than reaching an unsafe apply pass.
190
+ - Page-tree `/Parent` back-links and shared resource-hazard subtrees are handled
191
+ without repeated cross-expansion, keeping inventory linear as pages, Forms,
192
+ and aliases grow while preserving non-page `/Parent` reachability.
193
+
194
+ ### Safety
195
+
196
+ - Alternate-preview changes reject ambiguous roles, reserved/process names,
197
+ target-related DeviceN/NChannel use, malformed target name fields, signed or
198
+ restricted inputs, and unsafe output aliases before publication.
199
+ - In-memory and post-save checks bind inventory, content streams, the complete
200
+ document graph, and every requested preview definition to the planned change.
201
+ - Release metadata validation now binds each version's comparison link to the
202
+ immediately preceding dated release, and the maintainer checklist names every
203
+ local lock, metadata, size, corpus, benchmark, and artifact gate.
204
+
205
+ ## [0.3.0] - 2026-08-30
206
+
207
+ ### Added
208
+
209
+ - Atomic `rename` command for exact, case-sensitive spot-plate aliasing without
210
+ changing alternate color spaces, tint transforms, tint operands, resource
211
+ aliases, or content streams.
212
+ - Rename support for structurally consistent DeviceN/NChannel spot components,
213
+ `/Colorants`, `/MixingHints`, page `/SeparationInfo`, and normal PrinterMark
214
+ appearances.
215
+ - Role-aware DeviceN/NChannel inventory for arbitrary process component names,
216
+ canonical CMYK components, spot components, `/All`, and `/None`.
217
+ - Stable object identities and human-readable locations for every reachable
218
+ Separation and DeviceN definition.
219
+ - Exact-name dependency inventory for NChannel process/colorant/mixing-hint
220
+ entries, page `/SeparationInfo`, printer-mark colorants, and TrapNet
221
+ `/SeparationColorNames`.
222
+ - A pinned six-file public prepress release corpus spanning painted
223
+ Separations, DeviceCMYK and DeviceRGB alternates, DeviceN dependencies,
224
+ arbitrary NChannel process components, and multi-page definitions.
225
+ - Reproducible synthetic removal and rename documentation images.
226
+ - Automated GitHub release assets, SHA-256 checksums, and build-provenance
227
+ attestations after the full test and public-corpus gates pass.
228
+
229
+ ### Fixed
230
+
231
+ - Rename now accepts pikepdf's storage-only XMP packet reserialization in valid
232
+ PDF/A files while still rejecting RDF values, namespaces, comments, packet
233
+ identity/mutability, malformed wrapper grammar, and unrelated metadata
234
+ changes.
235
+
236
+ ### Safety
237
+
238
+ - Rename rejects missing or ambiguous sources, existing destinations, process
239
+ and reserved names, malformed name relationships, and unsupported TrapNet,
240
+ type 5 halftone, OPI, and PrinterMark rollover/down dependencies before any
241
+ output is published.
242
+ - Rename output is reopened and semantically verified before atomic replacement;
243
+ failed `--force` processing preserves the existing destination.
244
+ - Rename verification binds alternate spaces, tint transforms, content streams,
245
+ and dependency payloads to their semantic locations and rejects any change
246
+ beyond the planned name slots.
247
+ - Target-related DeviceN/NChannel, Process, MixingHints, Colorants, and
248
+ SeparationInfo relationships are validated before mutation.
249
+ - `remove --all` no longer treats custom-named NChannel process components as
250
+ spots.
251
+ - Removal fails closed when a selected name remains in a supported exact-name
252
+ prepress dependency, including pre-separated page metadata.
253
+ - The release build backend is exactly pinned and locked; unexpected release
254
+ filenames, symlinks, extra assets, version drift, and checksum changes fail
255
+ before publication.
256
+
257
+ ## [0.2.1] - 2026-08-29
258
+
259
+ First public beta release.
260
+
261
+ ### Added
262
+
263
+ - `list`, `check`, exact-name `remove`, and atomic `remove --all` commands.
264
+ - Removal of supported vector fills, strokes, combined paint, and text paint.
265
+ - Nested Form processing with context and nesting safeguards.
266
+ - A synthetic demo generator, before/after screenshots, and runtime-generated
267
+ test PDFs.
268
+
269
+ ### Safety
270
+
271
+ - Preserve process `/Cyan`, `/Magenta`, `/Yellow`, and `/Black` in `--all`.
272
+ - Preserve reserved `/All` and `/None` separations.
273
+ - Refuse signed, encrypted, restricted, malformed, and unsupported mutations.
274
+ - Refuse unresolved color-space and pattern resources.
275
+ - Reject output symlinks instead of following them under `--force`.
276
+ - Traverse deep PDF object graphs iteratively and reject excessive Form nesting
277
+ without a traceback or partial output.
278
+ - Verify saved output before atomic replacement and preserve an existing forced
279
+ destination whenever processing fails.
280
+
281
+ [Unreleased]: https://github.com/Hyperrick/spotpdf/compare/v0.7.0...HEAD
282
+ [0.7.0]: https://github.com/Hyperrick/spotpdf/compare/v0.6.0...v0.7.0
283
+ [0.6.0]: https://github.com/Hyperrick/spotpdf/compare/v0.5.0...v0.6.0
284
+ [0.5.0]: https://github.com/Hyperrick/spotpdf/compare/v0.4.0...v0.5.0
285
+ [0.4.0]: https://github.com/Hyperrick/spotpdf/compare/v0.3.0...v0.4.0
286
+ [0.3.0]: https://github.com/Hyperrick/spotpdf/compare/v0.2.1...v0.3.0
287
+ [0.2.1]: https://github.com/Hyperrick/spotpdf/releases/tag/v0.2.1
@@ -0,0 +1,195 @@
1
+ # Contributing to spotpdf
2
+
3
+ Thank you for helping make PDF spot-color processing safer and easier to audit.
4
+
5
+ ## Before opening an issue
6
+
7
+ - Search existing issues.
8
+ - Use the route described in [SUPPORT.md](SUPPORT.md) for usage questions, bugs,
9
+ feature requests, and private security reports.
10
+ - Include the `spotpdf`, Python, and operating-system versions.
11
+ - Include the exact command, stdout, stderr, expected result, and actual result.
12
+ - Say whether the PDF is signed, encrypted, or modification-restricted.
13
+ - Prefer a minimal PDF generated from code.
14
+
15
+ Never upload a confidential customer or production PDF to a public issue. If a
16
+ reproducer cannot be shared safely, describe its structure or create a synthetic
17
+ equivalent.
18
+
19
+ Security vulnerabilities belong in a private report as described in
20
+ [SECURITY.md](SECURITY.md), not a public issue.
21
+
22
+ ## Development setup
23
+
24
+ The documentation-image gate and rename render comparison require Poppler's
25
+ `pdftoppm` executable. Install Poppler before running the pull-request
26
+ checklist. On macOS with Homebrew:
27
+
28
+ ```bash
29
+ brew install poppler
30
+ ```
31
+
32
+ On Debian or Ubuntu:
33
+
34
+ ```bash
35
+ sudo apt-get update
36
+ sudo apt-get install --yes poppler-utils
37
+ ```
38
+
39
+ On other platforms, install a current [Poppler](https://poppler.freedesktop.org/)
40
+ build and put `pdftoppm` on `PATH`. In a POSIX shell, confirm the executable is
41
+ discoverable and print its version:
42
+
43
+ ```bash
44
+ command -v pdftoppm
45
+ pdftoppm -v
46
+ ```
47
+
48
+ In Windows PowerShell, run the equivalent checks:
49
+
50
+ ```powershell
51
+ Get-Command pdftoppm -ErrorAction Stop
52
+ pdftoppm -v
53
+ ```
54
+
55
+ Poppler alone covers the documentation-image gate and rename render comparison.
56
+ The convert render/plate comparison also requires qpdf and Ghostscript;
57
+ pull-request CI runs it in the Linux Python 3.13 test job. The maintainer-only
58
+ public corpus gate uses the same three tools and runs for releases. Its complete
59
+ setup remains in
60
+ [docs/public-corpus.md](docs/public-corpus.md#run-locally).
61
+
62
+ ```bash
63
+ git clone https://github.com/Hyperrick/spotpdf.git
64
+ cd spotpdf
65
+ uv sync --locked --dev
66
+ uv run python -m unittest discover -s tests -v
67
+ ```
68
+
69
+ Before submitting a pull request, run:
70
+
71
+ ```bash
72
+ uv sync --locked --dev --group release
73
+ uv lock --check
74
+ uv run ruff check .
75
+ uv run ruff format --check .
76
+ uv run python scripts/check_python_size.py
77
+ uv run python scripts/check_repository.py
78
+ uv run python scripts/create_docs_images.py
79
+ uv run python scripts/create_docs_images.py --check
80
+ uv run python -m unittest discover -s tests -v
81
+ uv run python scripts/benchmark_inventory.py --runs 3
82
+ artifact_dir="$(mktemp -d)"
83
+ uv build --no-build-isolation --out-dir "$artifact_dir"
84
+ uv run --no-sync python scripts/check_pypi_readme.py \
85
+ "$artifact_dir"/*.whl "$artifact_dir"/*.tar.gz
86
+ uv run --no-sync twine check --strict \
87
+ "$artifact_dir"/*.whl "$artifact_dir"/*.tar.gz
88
+ uv run python scripts/check_distribution.py "$artifact_dir"
89
+ uv run python scripts/smoke_distributions.py "$artifact_dir"
90
+ ```
91
+
92
+ The distribution check validates both archive contents and the canonical
93
+ Support and Security links embedded in each archive's Core Metadata.
94
+
95
+ Maintainers should also run the hash-pinned public corpus before a release:
96
+
97
+ ```bash
98
+ uv run python scripts/check_public_corpus.py
99
+ ```
100
+
101
+ See [docs/public-corpus.md](docs/public-corpus.md) for system dependencies,
102
+ attribution, licenses, and offline reproduction.
103
+
104
+ The inventory benchmark creates 64- and 128-spot PDFs only in a temporary
105
+ directory. Its page/Form parse counts are deterministic; timing and Python heap
106
+ measurements are diagnostic. See [docs/performance.md](docs/performance.md).
107
+
108
+ Documentation image checks regenerate all five synthetic visuals in a private
109
+ temporary directory. A deterministic SHA-256 manifest binds them to the demo,
110
+ locked environment, generator, and current spotpdf source. SVGs and that
111
+ manifest must match byte for byte. PNGs use bounded decoding and calibrated
112
+ pixel-drift limits so harmless Poppler antialiasing differences between
113
+ operating systems pass while meaningful visual changes fail: at most 2.5% of
114
+ pixels may have an RGB channel delta above 16, and the mean absolute channel
115
+ delta may not exceed 2.0.
116
+
117
+ ## Processing budget changes
118
+
119
+ Any counter, default, or preflight-order change must include:
120
+
121
+ - an exact-limit success test and a limit-plus-one failure test;
122
+ - small runtime-generated fixtures, including compressed content when decoded
123
+ size is involved;
124
+ - atomicity coverage proving every mutating command preserves an existing
125
+ `--force` destination and leaves no private temporary file;
126
+ - platform-stable assertions on counts and structured errors, not wall-clock,
127
+ RSS, compression ratios, or allocator behavior; and
128
+ - synchronized README, processing-budget, security, architecture, and changelog
129
+ documentation.
130
+
131
+ Keep limit configuration immutable and inject it per public library call. Do
132
+ not store per-job overrides or usage in mutable CLI/module globals. New limits
133
+ must document what they include, what they exclude, when they are checked, and
134
+ which native or operating-system work can occur before the overrun is observed.
135
+
136
+ ## JSON CLI contract changes
137
+
138
+ Treat `spotpdf.cli/v1` as a public automation API:
139
+
140
+ - add command fields through explicit serializers, never `dataclasses.asdict()`;
141
+ - keep PDF names and paths exact while sorting every set-derived array;
142
+ - add subprocess tests for exit code, stdout/stderr purity, canonical JSON,
143
+ Unicode and control characters, and the default text output;
144
+ - document every new result field or stable error code in
145
+ [JSON output and automation](docs/json-output.md) and `CHANGELOG.md`; and
146
+ - introduce a new schema identifier before removing, renaming, changing the
147
+ type of, or changing the meaning of an existing field.
148
+
149
+ Human-readable text remains the default. A JSON change must not weaken the
150
+ same atomic-output and fail-closed guarantees exercised by text mode.
151
+
152
+ ## PDF fixtures
153
+
154
+ Tests should build the smallest possible PDF with `pikepdf` inside a temporary
155
+ directory. Generated fixtures must not survive the test run.
156
+
157
+ If a third-party fixture is essential, prefer a commit-pinned external corpus
158
+ case over vendoring it. Document all of the following in the pull request:
159
+
160
+ - canonical source URL;
161
+ - author or project;
162
+ - exact license and redistribution permission;
163
+ - why a synthetic fixture cannot exercise the same behavior.
164
+
165
+ Do not add customer files, real print jobs, or PDFs with personal information.
166
+ The repository and distribution checks reject all tracked `*.pdf` files by
167
+ default, including mixed-case extensions. Repository-relative Markdown
168
+ destinations and repository-relative targets in HTML `href`, `src`, and
169
+ `srcset` attributes may only point to tracked files; ignored local files are
170
+ deliberately not accepted. External URLs and URL fragments are outside this
171
+ check's scope, but the file path before a fragment is still validated. The
172
+ package README has a stricter release gate: repository content must use absolute
173
+ URLs bound to the exact release tag so links and images also work on PyPI.
174
+ Contribution, support, security-policy, and release-process routes are the
175
+ deliberate live-policy exceptions.
176
+
177
+ ## Design rules
178
+
179
+ - Keep mutations atomic and fail closed on unsupported semantics.
180
+ - Add a regression test before or with every bug fix.
181
+ - Keep CLI behavior and exit codes backward compatible unless the change is
182
+ explicitly documented.
183
+ - Keep modules focused and Python files under 600 lines.
184
+ - Update `CHANGELOG.md` for user-visible behavior.
185
+ - Preserve vector data; do not introduce implicit rasterization.
186
+
187
+ See [AGENTS.md](AGENTS.md) for the complete organization guidelines and
188
+ [docs/architecture.md](docs/architecture.md) for current boundaries.
189
+
190
+ ## Pull requests
191
+
192
+ Keep each pull request narrowly scoped. Describe compatibility impact, include
193
+ tests and documentation, and disclose the origin and license of every fixture.
194
+ By contributing, you agree that your contribution is licensed under the
195
+ project's MIT License.
spotpdf-0.7.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 spotpdf contributors
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,32 @@
1
+ include CHANGELOG.md
2
+ include CONTRIBUTING.md
3
+ include LICENSE
4
+ include README.md
5
+ include SECURITY.md
6
+ include SUPPORT.md
7
+ include src/spotpdf/py.typed
8
+ include docs/architecture.md
9
+ include docs/compatibility.md
10
+ include docs/json-output.md
11
+ include docs/performance.md
12
+ include docs/processing-budgets.md
13
+ include docs/python-api.md
14
+ include docs/public-corpus.md
15
+ include docs/releasing.md
16
+ include docs/images/demo-after.png
17
+ include docs/images/demo-alternate.png
18
+ include docs/images/demo-before.png
19
+ include docs/images/demo-convert.svg
20
+ include docs/images/demo-rename.svg
21
+ include docs/images/generation.json
22
+ recursive-include corpus *.toml
23
+ recursive-include examples *.py
24
+ recursive-include scripts *.py
25
+ recursive-include tests *.py
26
+ global-exclude *.pdf
27
+ global-exclude .DS_Store
28
+ global-exclude *.py[cod]
29
+ prune .venv
30
+ prune in
31
+ prune out
32
+ prune tmp