psdparse 0.8.0__tar.gz → 0.9.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 (62) hide show
  1. {psdparse-0.8.0 → psdparse-0.9.0}/PKG-INFO +1 -1
  2. {psdparse-0.8.0 → psdparse-0.9.0}/docs/PYTHON_API.md +3 -0
  3. {psdparse-0.8.0 → psdparse-0.9.0}/docs/ROADMAP.md +16 -2
  4. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psddata.h +626 -608
  5. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdengine.cpp +274 -7
  6. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdengine.h +49 -0
  7. psdparse-0.9.0/psdparse/psdfile.cpp +821 -0
  8. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdfile.h +101 -0
  9. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdimage.cpp +1478 -1477
  10. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdparse.cpp +690 -649
  11. {psdparse-0.8.0 → psdparse-0.9.0}/pyproject.toml +1 -1
  12. {psdparse-0.8.0 → psdparse-0.9.0}/python/psdparse_module.cpp +18 -54
  13. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit.py +45 -0
  14. psdparse-0.8.0/psdparse/psdfile.cpp +0 -373
  15. {psdparse-0.8.0 → psdparse-0.9.0}/.gitignore +0 -0
  16. {psdparse-0.8.0 → psdparse-0.9.0}/CMakeLists.txt +0 -0
  17. {psdparse-0.8.0 → psdparse-0.9.0}/CMakePresets.json +0 -0
  18. {psdparse-0.8.0 → psdparse-0.9.0}/LICENSE +0 -0
  19. {psdparse-0.8.0 → psdparse-0.9.0}/Makefile +0 -0
  20. {psdparse-0.8.0 → psdparse-0.9.0}/README.md +0 -0
  21. {psdparse-0.8.0 → psdparse-0.9.0}/docs/ARCHITECTURE.md +0 -0
  22. {psdparse-0.8.0 → psdparse-0.9.0}/docs/SUPPORT.md +0 -0
  23. {psdparse-0.8.0 → psdparse-0.9.0}/examples/README.md +0 -0
  24. {psdparse-0.8.0 → psdparse-0.9.0}/examples/composite.py +0 -0
  25. {psdparse-0.8.0 → psdparse-0.9.0}/examples/extract_layers.py +0 -0
  26. {psdparse-0.8.0 → psdparse-0.9.0}/examples/variations.py +0 -0
  27. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/CMakeLists.txt +0 -0
  28. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/bmp.cpp +0 -0
  29. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psd_cli.cpp +0 -0
  30. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdbase.h +0 -0
  31. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psddesc.cpp +0 -0
  32. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psddesc.h +0 -0
  33. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdlayer.cpp +0 -0
  34. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdlayer.h +0 -0
  35. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdparse.h +0 -0
  36. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdresource.cpp +0 -0
  37. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdresource.h +0 -0
  38. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdwrite.cpp +0 -0
  39. {psdparse-0.8.0 → psdparse-0.9.0}/psdparse/psdwrite.h +0 -0
  40. {psdparse-0.8.0 → psdparse-0.9.0}/python/CMakeLists.txt +0 -0
  41. {psdparse-0.8.0 → psdparse-0.9.0}/tests/conftest.py +0 -0
  42. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_colormodes.py +0 -0
  43. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_comps.py +0 -0
  44. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_create.py +0 -0
  45. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_descriptors.py +0 -0
  46. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_effects.py +0 -0
  47. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_mask.py +0 -0
  48. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_mask_pixels.py +0 -0
  49. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_name.py +0 -0
  50. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_pixels.py +0 -0
  51. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_run_style.py +0 -0
  52. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_edit_text.py +0 -0
  53. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_header.py +0 -0
  54. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_images.py +0 -0
  55. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_layers.py +0 -0
  56. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_mask_extras.py +0 -0
  57. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_merged.py +0 -0
  58. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_metadata.py +0 -0
  59. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_resources.py +0 -0
  60. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_save.py +0 -0
  61. {psdparse-0.8.0 → psdparse-0.9.0}/tests/test_text.py +0 -0
  62. {psdparse-0.8.0 → psdparse-0.9.0}/tools/psd_export.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: psdparse
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Fast PSD (Photoshop) reader/writer — C++17 core with pybind11 bindings
5
5
  Keywords: psd,photoshop,parser,image,graphics
6
6
  Author-Email: wamsoft <wtnbgo@gmail.com>
@@ -331,6 +331,9 @@ new_i = p.copy_layer_from(src, j, dest_index=-1) # copy layer j from another PS
331
331
  ```
332
332
 
333
333
  Notes:
334
+ - `duplicate_layer` / `copy_layer_from` assign the copy a **fresh `layer_id`**
335
+ (max existing lyid + 1, like Photoshop) so layer IDs stay unique within the
336
+ document; the `lyid` additional-info block is rewritten on save.
334
337
  - `delete_layer` / `duplicate_layer` on a single layer are exact. Deleting **one
335
338
  half of a group's FOLDER/HIDDEN divider pair unbalances the group** — delete
336
339
  whole groups (both dividers + contents) for clean nesting. (Unbalanced results
@@ -3,12 +3,12 @@
3
3
  For a feature-by-feature account of what is and isn't supported today, see
4
4
  [SUPPORT.md](SUPPORT.md). This file tracks planned work.
5
5
 
6
- ## Current state (2026-08-02, v0.8.0)
6
+ ## Current state (2026-08-12, v0.8.1)
7
7
 
8
8
  - ✅ Pure C++17 parser (no Boost)
9
9
  - ✅ mmap + StreamReader / Source abstraction
10
10
  - ✅ Python bindings (pybind11)
11
- - ✅ pytest regression suite (137 tests)
11
+ - ✅ pytest regression suite (140 tests)
12
12
  - ✅ Python composite recipes (`examples/`) + `set_merged_image`; layer comps exposed (`layer.comp_states`)
13
13
  - ✅ Round-trip PSD save (byte-identical)
14
14
  - ✅ Edit & save: structure / pixels / mask / parameters / effects / text / new-from-scratch (E1–E6, byte-exact re-serialization)
@@ -91,6 +91,20 @@ Editing text content/style means writing the `TySh` block back: re-serializing t
91
91
 
92
92
  ## Other future work
93
93
 
94
+ - ✅ **`duplicate_layer` assigns a fresh `lyid`.** *Done 2026-08-12.* The
95
+ duplicated layer used to copy the source's `lyid` (layer ID) verbatim, so the
96
+ output PSD contained duplicate layer IDs — Photoshop itself assigns a *new*
97
+ ID when duplicating. Downstream tools that use `lyid` as a persistent layer
98
+ identity (e.g. elements_console's uitool reference resolution) then saw
99
+ ambiguous matches. Fix (`psdfile.cpp`): `assignFreshLayerId` sets
100
+ `layerId = max existing lyid + 1` and replaces (or appends) the `lyid`
101
+ additional-info entry on the copy, flipping `useRawBytes=false` so save
102
+ re-serializes it; applied in both `duplicateLayer` and `copyLayerFrom` (the
103
+ latter picks an ID unique within the *destination* document). `add_layer`
104
+ already picked max+1 — verified. Tests (`tests/test_edit.py`): duplicate →
105
+ all `layer.layer_id` distinct → save → reload → still distinct; double
106
+ duplicate cross-checked via psd-tools tagged blocks; `copy_layer_from`
107
+ uniqueness in the destination.
94
108
  - ✅ **Text layer content extraction (`TySh` type-tool additional info).** *Done 2026-07-27.* `lay.text` returns `{"text", "orientation", "justification", "transform", "runs":[{"length","font","size_px","color","tracking","kerning","auto_kerning"}]}` (or `None` for non-text layers). Implementation: `psddesc` now reads `tdta` raw data (length-prefixed), `loadLayerTypeTool` (`psdlayer.cpp`) parses the `TySh` header + text descriptor, and `psdengine.cpp` parses the embedded Adobe *EngineData* mini-language (FontSet / StyleRun / ParagraphRun) into per-run styling. Validated against `tests/data/fontsample.psd` (multi-font/size/color, **vertical**, emoji, tracking) — see `tests/test_text.py`.
95
109
 
96
110
  **Deferred (need targeted sample PSDs, next turn):**