psdparse 0.2.0__tar.gz → 0.2.2__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. {psdparse-0.2.0 → psdparse-0.2.2}/PKG-INFO +1 -1
  2. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psddata.h +16 -2
  3. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdengine.cpp +61 -6
  4. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdparse.cpp +29 -1
  5. {psdparse-0.2.0 → psdparse-0.2.2}/pyproject.toml +1 -1
  6. {psdparse-0.2.0 → psdparse-0.2.2}/python/psdparse_module.cpp +12 -2
  7. {psdparse-0.2.0 → psdparse-0.2.2}/.gitignore +0 -0
  8. {psdparse-0.2.0 → psdparse-0.2.2}/CMakeLists.txt +0 -0
  9. {psdparse-0.2.0 → psdparse-0.2.2}/CMakePresets.json +0 -0
  10. {psdparse-0.2.0 → psdparse-0.2.2}/LICENSE +0 -0
  11. {psdparse-0.2.0 → psdparse-0.2.2}/Makefile +0 -0
  12. {psdparse-0.2.0 → psdparse-0.2.2}/README.md +0 -0
  13. {psdparse-0.2.0 → psdparse-0.2.2}/docs/ARCHITECTURE.md +0 -0
  14. {psdparse-0.2.0 → psdparse-0.2.2}/docs/PYTHON_API.md +0 -0
  15. {psdparse-0.2.0 → psdparse-0.2.2}/docs/ROADMAP.md +0 -0
  16. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/CMakeLists.txt +0 -0
  17. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/bmp.cpp +0 -0
  18. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psd_cli.cpp +0 -0
  19. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdbase.h +0 -0
  20. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psddesc.cpp +0 -0
  21. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psddesc.h +0 -0
  22. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdengine.h +0 -0
  23. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdfile.cpp +0 -0
  24. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdfile.h +0 -0
  25. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdimage.cpp +0 -0
  26. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdlayer.cpp +0 -0
  27. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdlayer.h +0 -0
  28. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdparse.h +0 -0
  29. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdresource.cpp +0 -0
  30. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdresource.h +0 -0
  31. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdwrite.cpp +0 -0
  32. {psdparse-0.2.0 → psdparse-0.2.2}/psdparse/psdwrite.h +0 -0
  33. {psdparse-0.2.0 → psdparse-0.2.2}/python/CMakeLists.txt +0 -0
  34. {psdparse-0.2.0 → psdparse-0.2.2}/tests/conftest.py +0 -0
  35. {psdparse-0.2.0 → psdparse-0.2.2}/tests/test_header.py +0 -0
  36. {psdparse-0.2.0 → psdparse-0.2.2}/tests/test_images.py +0 -0
  37. {psdparse-0.2.0 → psdparse-0.2.2}/tests/test_layers.py +0 -0
  38. {psdparse-0.2.0 → psdparse-0.2.2}/tests/test_save.py +0 -0
  39. {psdparse-0.2.0 → psdparse-0.2.2}/tests/test_text.py +0 -0
  40. {psdparse-0.2.0 → psdparse-0.2.2}/tools/psd_export.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: psdparse
3
- Version: 0.2.0
3
+ Version: 0.2.2
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>
@@ -157,6 +157,8 @@ namespace psd {
157
157
  int width;
158
158
  int depth;
159
159
  int mode;
160
+ double hres = 72.0; // 水平解像度 dpi (image resource 1005)。 既定 72。
161
+ double vres = 72.0; // 垂直解像度 dpi。
160
162
  };
161
163
 
162
164
  // RGBAカラー
@@ -403,10 +405,21 @@ namespace psd {
403
405
  int tracking; // トラッキング (字送り, 1/1000 em)
404
406
  int kerning; // 手動カーニング
405
407
  bool autoKerning; // 自動カーニング (メトリクス/オプティカル) 有効
408
+ bool sizeInherited; // FontSize を既定 StyleSheet から継承したか。
409
+ // 継承分は nominal pt なので dpi/72 で px 化する
410
+ // (明示 run の FontSize は既に解決済み px)。内部用。
406
411
 
407
412
  TextStyleRun()
408
413
  : length(0), fontSize(0.0f), color{0,0,0,1}, hasColor(false),
409
- tracking(0), kerning(0), autoKerning(false) {}
414
+ tracking(0), kerning(0), autoKerning(false), sizeInherited(false) {}
415
+ };
416
+
417
+ // 段落単位の情報 (EngineDict/ParagraphRun)。 段落は本文中の改行 (\r) 区切り。
418
+ struct TextParagraph {
419
+ int length; // 段落の文字数 (UTF-16 コードユニット, RunLengthArray)
420
+ int justification; // 行揃え 0=左 1=右 2=中央 (3..=両端揃え系)
421
+
422
+ TextParagraph() : length(0), justification(0) {}
410
423
  };
411
424
 
412
425
  // テキストレイヤ情報 (追加レイヤ情報 'TySh' 由来)。
@@ -415,8 +428,9 @@ namespace psd {
415
428
  u16str text; // 本文全体 (改行は \r)
416
429
  double transform[6]; // アフィン変換 xx,xy,yx,yy,tx,ty
417
430
  std::string orientation; // "horizontal" / "vertical"
418
- int justification; // 段落の行揃え 0=左 1=右 2=中央 (先頭段落)
431
+ int justification; // 段落の行揃え 0=左 1=右 2=中央 (先頭段落; 後方互換)
419
432
  std::vector<TextStyleRun> runs;
433
+ std::vector<TextParagraph> paragraphs; // 段落別 (行揃えが段落で変わる box text 用)
420
434
 
421
435
  TextLayerData()
422
436
  : present(false), transform{1,0,0,1,0,0},
@@ -204,6 +204,37 @@ namespace {
204
204
  }
205
205
  }
206
206
 
207
+ // 既定 StyleSheet (ResourceDict/StyleSheetSet[0]/StyleSheetData)。
208
+ // box text の基準 run は FontSize/Font 等の StyleSheetData を持たず、 この既定を
209
+ // 継承する。 これを読まないと FontSize=0 になる (実表示は既定サイズ)。
210
+ float defSize = 0.0f; int defFontIdx = -1; int defTracking = 0;
211
+ float defColor[4] = {0, 0, 0, 1}; bool defHasColor = false;
212
+ {
213
+ Node *rd2 = dget(root, "ResourceDict");
214
+ Node *sss = dget(rd2, "StyleSheetSet");
215
+ Node *dssd = 0;
216
+ if (sss && sss->kind == Node::ARRAY && !sss->arr.empty()) {
217
+ dssd = dget(sss->arr[0], "StyleSheetData");
218
+ if (!dssd) dssd = dget(dget(sss->arr[0], "StyleSheet"), "StyleSheetData");
219
+ }
220
+ if (dssd) {
221
+ Node *f = dget(dssd, "Font");
222
+ if (f && f->kind == Node::NUMBER) defFontIdx = (int)f->num;
223
+ Node *fs = dget(dssd, "FontSize");
224
+ if (fs && fs->kind == Node::NUMBER) defSize = (float)fs->num;
225
+ Node *tk = dget(dssd, "Tracking");
226
+ if (tk && tk->kind == Node::NUMBER) defTracking = (int)tk->num;
227
+ Node *vals = dget(dget(dssd, "FillColor"), "Values");
228
+ if (vals && vals->kind == Node::ARRAY && vals->arr.size() >= 4) {
229
+ defColor[3] = (float)vals->arr[0]->num; // A
230
+ defColor[0] = (float)vals->arr[1]->num; // R
231
+ defColor[1] = (float)vals->arr[2]->num; // G
232
+ defColor[2] = (float)vals->arr[3]->num; // B
233
+ defHasColor = true;
234
+ }
235
+ }
236
+ }
237
+
207
238
  // ラン単位スタイル (EngineDict/StyleRun)
208
239
  if (engine) {
209
240
  Node *styleRun = dget(engine, "StyleRun");
@@ -213,6 +244,16 @@ namespace {
213
244
  for (size_t i = 0; i < runArray->arr.size(); i++) {
214
245
  Node *ssd = dget(dget(runArray->arr[i], "StyleSheet"), "StyleSheetData");
215
246
  TextStyleRun r;
247
+ // 既定を先に適用 (未指定フィールドの継承)。
248
+ r.fontSize = defSize;
249
+ r.sizeInherited = true; // 既定継承 (下で run が上書きすれば false)
250
+ r.tracking = defTracking;
251
+ if (defFontIdx >= 0 && defFontIdx < (int)fonts.size()) r.font = fonts[defFontIdx];
252
+ if (defHasColor) {
253
+ r.color[0] = defColor[0]; r.color[1] = defColor[1];
254
+ r.color[2] = defColor[2]; r.color[3] = defColor[3];
255
+ r.hasColor = true;
256
+ }
216
257
  if (runLen && runLen->kind == Node::ARRAY && i < runLen->arr.size() &&
217
258
  runLen->arr[i]->kind == Node::NUMBER) {
218
259
  r.length = (int)runLen->arr[i]->num;
@@ -224,7 +265,10 @@ namespace {
224
265
  if (idx >= 0 && idx < (int)fonts.size()) r.font = fonts[idx];
225
266
  }
226
267
  Node *fsz = dget(ssd, "FontSize");
227
- if (fsz && fsz->kind == Node::NUMBER) r.fontSize = (float)fsz->num;
268
+ if (fsz && fsz->kind == Node::NUMBER) {
269
+ r.fontSize = (float)fsz->num; // 明示 = 解決済み px
270
+ r.sizeInherited = false;
271
+ }
228
272
  Node *trk = dget(ssd, "Tracking");
229
273
  if (trk && trk->kind == Node::NUMBER) r.tracking = (int)trk->num;
230
274
  Node *krn = dget(ssd, "Kerning");
@@ -245,12 +289,23 @@ namespace {
245
289
  }
246
290
  }
247
291
 
248
- // 行揃え (先頭段落の Justification)
249
- Node *paraArr = dget(dget(engine, "ParagraphRun"), "RunArray");
292
+ // 段落別の行揃え (ParagraphRun)。 out.justification は先頭段落 (後方互換)
293
+ // out.paragraphs に全段落分 (length + justification) を格納する。
294
+ Node *paraRun = dget(engine, "ParagraphRun");
295
+ Node *paraArr = dget(paraRun, "RunArray");
296
+ Node *paraLen = dget(paraRun, "RunLengthArray");
250
297
  if (paraArr && paraArr->kind == Node::ARRAY && !paraArr->arr.empty()) {
251
- Node *props = dget(dget(paraArr->arr[0], "ParagraphSheet"), "Properties");
252
- Node *just = dget(props, "Justification");
253
- if (just && just->kind == Node::NUMBER) out.justification = (int)just->num;
298
+ for (size_t i = 0; i < paraArr->arr.size(); i++) {
299
+ TextParagraph p;
300
+ if (paraLen && paraLen->kind == Node::ARRAY && i < paraLen->arr.size() &&
301
+ paraLen->arr[i]->kind == Node::NUMBER)
302
+ p.length = (int)paraLen->arr[i]->num;
303
+ Node *props = dget(dget(paraArr->arr[i], "ParagraphSheet"), "Properties");
304
+ Node *just = dget(props, "Justification");
305
+ if (just && just->kind == Node::NUMBER) p.justification = (int)just->num;
306
+ out.paragraphs.push_back(p);
307
+ }
308
+ out.justification = out.paragraphs[0].justification; // 後方互換
254
309
  }
255
310
  }
256
311
 
@@ -35,11 +35,22 @@ Data::processParsed()
35
35
  success = loadResourceLayerComps(*this, res);
36
36
  break;
37
37
 
38
+ case 1005: { // 0x03ED -- ResolutionInfo。 hRes/vRes は Fixed 16.16 の dpi。
39
+ if (res.data) {
40
+ int32_t h = res.data->getInt32(); // hRes (Fixed 16.16)
41
+ (void)res.data->getInt16(); // hResUnit
42
+ (void)res.data->getInt16(); // widthUnit
43
+ int32_t v = res.data->getInt32(); // vRes (Fixed 16.16)
44
+ if (h > 0) header.hres = (double)(uint32_t)h / 65536.0;
45
+ if (v > 0) header.vres = (double)(uint32_t)v / 65536.0;
46
+ }
47
+ break;
48
+ }
49
+
38
50
  default: // 未実装はそのままスルー
39
51
  case 1000: // 0x03E8 -- (Obsolete--Photoshop 2.0 only ) Contains five 2-byte values: number of channels, rows, columns, depth, and mode
40
52
  case 1001: // 0x03E9 -- Macintosh print manager print info record
41
53
  case 1003: // 0x03EB -- (Obsolete--Photoshop 2.0 only ) Indexed color table
42
- case 1005: // 0x03ED -- ResolutionInfo structure. See Appendix A in Photoshop API Guide.pdf.
43
54
  case 1006: // 0x03EE -- Names of the alpha channels as a series of Pascal strings.
44
55
  case 1007: // 0x03EF -- (Obsolete) See ID 1077DisplayInfo structure. See Appendix A in Photoshop API Guide.pdf.
45
56
  case 1008: // 0x03F0 -- The caption as a Pascal string.
@@ -295,6 +306,23 @@ Data::processParsed()
295
306
  }
296
307
  }
297
308
 
309
+ // テキスト run の FontSize を px に正規化する (レイヤ解析後・解像度確定後)。 明示
310
+ // run の FontSize は既に解決済み px だが、 既定 StyleSheet から継承した分は nominal
311
+ // pt なので dpi/72 を掛ける (300dpi なら 12pt → 50px)。
312
+ {
313
+ double sc = header.hres / 72.0;
314
+ if (sc != 1.0) {
315
+ for (uint32_t li = 0; li < layerList.size(); li++) {
316
+ TextLayerData &td = layerList[li].textData;
317
+ if (!td.present) continue;
318
+ for (size_t ri = 0; ri < td.runs.size(); ri++) {
319
+ if (td.runs[ri].sizeInherited)
320
+ td.runs[ri].fontSize *= (float)sc;
321
+ }
322
+ }
323
+ }
324
+ }
325
+
298
326
  return success;
299
327
  }
300
328
 
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "psdparse"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Fast PSD (Photoshop) reader/writer — C++17 core with pybind11 bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -52,7 +52,7 @@ py::object layerText(const psd::LayerInfo &l) {
52
52
  py::dict rd;
53
53
  rd["length"] = r.length; // UTF-16 code units covered by this run
54
54
  rd["font"] = py::cast(r.font); // resolved font-set name
55
- rd["size_px"] = r.fontSize; // pt
55
+ rd["size_px"] = r.fontSize; // px (文書解像度で換算済み; 継承分は pt×dpi/72)
56
56
  rd["tracking"] = r.tracking; // 1/1000 em
57
57
  rd["kerning"] = r.kerning; // manual kerning
58
58
  rd["auto_kerning"] = r.autoKerning; // metrics/optical kerning on
@@ -63,6 +63,14 @@ py::object layerText(const psd::LayerInfo &l) {
63
63
  runs.append(rd);
64
64
  }
65
65
  d["runs"] = runs;
66
+ py::list paras;
67
+ for (const auto &p : t.paragraphs) {
68
+ py::dict pd;
69
+ pd["length"] = p.length; // UTF-16 code units
70
+ pd["justification"] = p.justification; // 0=left 1=right 2=center
71
+ paras.append(pd);
72
+ }
73
+ d["paragraphs"] = paras; // 段落別行揃え (段落=改行区切り)
66
74
  return std::move(d);
67
75
  }
68
76
 
@@ -134,7 +142,9 @@ PYBIND11_MODULE(psdparse, m) {
134
142
  .def_readonly("height", &psd::Header::height)
135
143
  .def_readonly("width", &psd::Header::width)
136
144
  .def_readonly("depth", &psd::Header::depth)
137
- .def_readonly("mode", &psd::Header::mode);
145
+ .def_readonly("mode", &psd::Header::mode)
146
+ .def_readonly("hres", &psd::Header::hres) // 水平解像度 dpi (既定 72)
147
+ .def_readonly("vres", &psd::Header::vres); // 垂直解像度 dpi
138
148
 
139
149
  py::class_<psd::ChannelInfo>(m, "ChannelInfo")
140
150
  .def_readonly("id", &psd::ChannelInfo::id)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes