@nice2dev/ui-graphics 1.3.15 → 1.3.17

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 (294) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,334 +1,895 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var ui = require('@nice2dev/ui');
5
- var uiCore = require('@nice2dev/ui-core');
6
- var React = require('react');
7
- var i18n = require('../core/i18n.js');
8
-
9
- /** Built-in guided tour (EN inline + i18n keys → PL and other locales). */
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const ui = require("@nice2dev/ui");
5
+ const uiCore = require("@nice2dev/ui-core");
6
+ const React = require("react");
7
+ const i18n = require("../core/i18n.js");
10
8
  const FONT_EDITOR_TOUR = [
11
- {
12
- target: '.nice-font-editor',
13
- titleKey: 'tutorial.fontEditor.intro.title',
14
- title: 'Font editor',
15
- contentKey: 'tutorial.fontEditor.intro.content',
16
- content: 'Edit glyphs on the unicode grid, adjust font metrics and per-glyph paths and bearings, then export the font.',
17
- placement: 'auto',
18
- },
9
+ {
10
+ target: ".nice-font-editor",
11
+ titleKey: "tutorial.fontEditor.intro.title",
12
+ title: "Font editor",
13
+ contentKey: "tutorial.fontEditor.intro.content",
14
+ content: "Edit glyphs on the unicode grid, adjust font metrics and per-glyph paths and bearings, then export the font.",
15
+ placement: "auto"
16
+ }
19
17
  ];
20
- // ── Defaults ────────────────────────────────────────────────────
21
18
  const DEFAULT_METRICS = {
22
- unitsPerEm: 1000,
23
- ascent: 800,
24
- descent: -200,
25
- lineGap: 0,
26
- xHeight: 500,
27
- capHeight: 700,
19
+ unitsPerEm: 1e3,
20
+ ascent: 800,
21
+ descent: -200,
22
+ lineGap: 0,
23
+ xHeight: 500,
24
+ capHeight: 700
28
25
  };
29
- const BASIC_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,:;!?-\'"()[]{}@#$%&*+=/\\<>~`^|_';
26
+ const BASIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,:;!?-'\"()[]{}@#$%&*+=/\\<>~`^|_";
30
27
  function createDefaultGlyphs() {
31
- return Array.from(BASIC_CHARS).map((char) => ({
32
- unicode: char.codePointAt(0),
33
- char,
34
- name: char === ' '
35
- ? 'space'
36
- : `uni${char.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`,
37
- path: '',
38
- advanceWidth: 500,
39
- leftBearing: 50,
40
- }));
28
+ return Array.from(BASIC_CHARS).map((char) => ({
29
+ unicode: char.codePointAt(0),
30
+ char,
31
+ name: char === " " ? "space" : `uni${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`,
32
+ path: "",
33
+ advanceWidth: 500,
34
+ leftBearing: 50
35
+ }));
41
36
  }
42
- // ── SVG Font Export ─────────────────────────────────────────────
43
37
  function exportToSvgFont(data) {
44
- const glyphElements = data.glyphs
45
- .filter((g) => g.path)
46
- .map((g) => ` <glyph unicode="&#x${g.unicode.toString(16)};" glyph-name="${g.name}" d="${g.path}" horiz-adv-x="${g.advanceWidth}" />`)
47
- .join('\n');
48
- return `<?xml version="1.0" standalone="no"?>
49
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
50
- <svg xmlns="http://www.w3.org/2000/svg">
51
- <defs>
52
- <font id="${data.family}" horiz-adv-x="${data.metrics.unitsPerEm}">
53
- <font-face
54
- font-family="${data.family}"
55
- font-weight="400"
56
- font-style="normal"
57
- units-per-em="${data.metrics.unitsPerEm}"
58
- ascent="${data.metrics.ascent}"
59
- descent="${data.metrics.descent}"
60
- x-height="${data.metrics.xHeight}"
61
- cap-height="${data.metrics.capHeight}"
62
- />
63
- <missing-glyph horiz-adv-x="${data.metrics.unitsPerEm * 0.5}" />
64
- ${glyphElements}
65
- </font>
66
- </defs>
38
+ const glyphElements = data.glyphs.filter((g) => g.path).map(
39
+ (g) => ` <glyph unicode="&#x${g.unicode.toString(16)};" glyph-name="${g.name}" d="${g.path}" horiz-adv-x="${g.advanceWidth}" />`
40
+ ).join("\n");
41
+ return `<?xml version="1.0" standalone="no"?>
42
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
43
+ <svg xmlns="http://www.w3.org/2000/svg">
44
+ <defs>
45
+ <font id="${data.family}" horiz-adv-x="${data.metrics.unitsPerEm}">
46
+ <font-face
47
+ font-family="${data.family}"
48
+ font-weight="400"
49
+ font-style="normal"
50
+ units-per-em="${data.metrics.unitsPerEm}"
51
+ ascent="${data.metrics.ascent}"
52
+ descent="${data.metrics.descent}"
53
+ x-height="${data.metrics.xHeight}"
54
+ cap-height="${data.metrics.capHeight}"
55
+ />
56
+ <missing-glyph horiz-adv-x="${data.metrics.unitsPerEm * 0.5}" />
57
+ ${glyphElements}
58
+ </font>
59
+ </defs>
67
60
  </svg>`;
68
61
  }
69
- // ── Hook ────────────────────────────────────────────────────────
70
62
  function useFontEditor(initialName) {
71
- const [fontName, setFontName] = React.useState(initialName);
72
- const [metrics, setMetrics] = React.useState(DEFAULT_METRICS);
73
- const [glyphs, setGlyphs] = React.useState(createDefaultGlyphs);
74
- const [selectedGlyph, setSelectedGlyph] = React.useState(null);
75
- const [previewText, setPreviewText] = React.useState('The quick brown fox jumps over the lazy dog');
76
- const [historyIdx, setHistoryIdx] = React.useState(0);
77
- const historyRef = React.useRef([]);
78
- React.useEffect(() => {
79
- historyRef.current = [JSON.parse(JSON.stringify(glyphs))];
80
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
81
- const pushHistory = React.useCallback((newGlyphs) => {
82
- historyRef.current = historyRef.current.slice(0, historyIdx + 1);
83
- historyRef.current.push(JSON.parse(JSON.stringify(newGlyphs)));
84
- if (historyRef.current.length > 40) {
85
- historyRef.current.shift();
86
- }
87
- setHistoryIdx(historyRef.current.length - 1);
88
- }, [historyIdx]);
89
- const updateGlyph = React.useCallback((unicode, updates) => {
90
- const newGlyphs = glyphs.map((g) => (g.unicode === unicode ? { ...g, ...updates } : g));
91
- setGlyphs(newGlyphs);
92
- pushHistory(newGlyphs);
93
- }, [glyphs, pushHistory]);
94
- const addGlyph = React.useCallback((char) => {
95
- const unicode = char.codePointAt(0);
96
- if (glyphs.some((g) => g.unicode === unicode)) {
97
- return;
63
+ const [fontName, setFontName] = React.useState(initialName);
64
+ const [metrics, setMetrics] = React.useState(DEFAULT_METRICS);
65
+ const [glyphs, setGlyphs] = React.useState(createDefaultGlyphs);
66
+ const [selectedGlyph, setSelectedGlyph] = React.useState(null);
67
+ const [previewText, setPreviewText] = React.useState(
68
+ "The quick brown fox jumps over the lazy dog"
69
+ );
70
+ const [historyIdx, setHistoryIdx] = React.useState(0);
71
+ const historyRef = React.useRef([]);
72
+ React.useEffect(() => {
73
+ historyRef.current = [JSON.parse(JSON.stringify(glyphs))];
74
+ }, []);
75
+ const pushHistory = React.useCallback(
76
+ (newGlyphs) => {
77
+ historyRef.current = historyRef.current.slice(0, historyIdx + 1);
78
+ historyRef.current.push(JSON.parse(JSON.stringify(newGlyphs)));
79
+ if (historyRef.current.length > 40) {
80
+ historyRef.current.shift();
81
+ }
82
+ setHistoryIdx(historyRef.current.length - 1);
83
+ },
84
+ [historyIdx]
85
+ );
86
+ const updateGlyph = React.useCallback(
87
+ (unicode, updates) => {
88
+ const newGlyphs = glyphs.map((g) => g.unicode === unicode ? { ...g, ...updates } : g);
89
+ setGlyphs(newGlyphs);
90
+ pushHistory(newGlyphs);
91
+ },
92
+ [glyphs, pushHistory]
93
+ );
94
+ const addGlyph = React.useCallback(
95
+ (char) => {
96
+ const unicode = char.codePointAt(0);
97
+ if (glyphs.some((g) => g.unicode === unicode)) {
98
+ return;
99
+ }
100
+ const newGlyphs = [
101
+ ...glyphs,
102
+ {
103
+ unicode,
104
+ char,
105
+ name: `uni${unicode.toString(16).toUpperCase().padStart(4, "0")}`,
106
+ path: "",
107
+ advanceWidth: 500,
108
+ leftBearing: 50
98
109
  }
99
- const newGlyphs = [
100
- ...glyphs,
110
+ ];
111
+ setGlyphs(newGlyphs);
112
+ pushHistory(newGlyphs);
113
+ },
114
+ [glyphs, pushHistory]
115
+ );
116
+ const toFontData = React.useCallback(
117
+ () => ({
118
+ name: fontName,
119
+ family: fontName.replace(/\s+/g, ""),
120
+ metrics,
121
+ glyphs
122
+ }),
123
+ [fontName, metrics, glyphs]
124
+ );
125
+ return {
126
+ fontName,
127
+ setFontName,
128
+ metrics,
129
+ setMetrics,
130
+ glyphs,
131
+ selectedGlyph,
132
+ setSelectedGlyph,
133
+ updateGlyph,
134
+ addGlyph,
135
+ previewText,
136
+ setPreviewText,
137
+ toFontData
138
+ };
139
+ }
140
+ const NiceFontEditor = ({
141
+ fontName = "MyFont",
142
+ onSave,
143
+ className = "",
144
+ tutorial
145
+ }) => {
146
+ const { t } = i18n.useNiceTranslation();
147
+ const editor = useFontEditor(fontName);
148
+ const [drawMode, setDrawMode] = React.useState("select");
149
+ const [pathPoints, setPathPoints] = React.useState([]);
150
+ const [tab, setTab] = React.useState("glyph");
151
+ const [kerningPairs, setKerningPairs] = React.useState([]);
152
+ const [newKernLeft, setNewKernLeft] = React.useState("A");
153
+ const [newKernRight, setNewKernRight] = React.useState("V");
154
+ const [showGrid, setShowGrid] = React.useState(true);
155
+ const handleSave = () => onSave?.(editor.toFontData());
156
+ const handleExportSvg = () => {
157
+ const data = editor.toFontData();
158
+ const svg = exportToSvgFont(data);
159
+ const blob = new Blob([svg], { type: "image/svg+xml" });
160
+ const url = URL.createObjectURL(blob);
161
+ const a = document.createElement("a");
162
+ a.href = url;
163
+ a.download = `${data.family}.svg`;
164
+ a.click();
165
+ URL.revokeObjectURL(url);
166
+ };
167
+ const selectedG = editor.selectedGlyph !== null ? editor.glyphs.find((g) => g.unicode === editor.selectedGlyph) ?? null : null;
168
+ const em = editor.metrics.unitsPerEm;
169
+ const handleGlyphCanvasClick = (e) => {
170
+ if (drawMode !== "pen" || !selectedG) {
171
+ return;
172
+ }
173
+ const svg = e.currentTarget;
174
+ const rect = svg.getBoundingClientRect();
175
+ const x = Math.round((e.clientX - rect.left) / rect.width * em);
176
+ const y = Math.round((e.clientY - rect.top) / rect.height * em);
177
+ const newPoints = [...pathPoints, { x, y }];
178
+ setPathPoints(newPoints);
179
+ if (newPoints.length >= 2) {
180
+ let d = `M ${newPoints[0].x} ${newPoints[0].y}`;
181
+ for (let i = 1; i < newPoints.length; i++) {
182
+ d += ` L ${newPoints[i].x} ${newPoints[i].y}`;
183
+ }
184
+ d += " Z";
185
+ editor.updateGlyph(selectedG.unicode, { path: d });
186
+ }
187
+ };
188
+ const clearPath = () => {
189
+ setPathPoints([]);
190
+ if (selectedG) {
191
+ editor.updateGlyph(selectedG.unicode, { path: "" });
192
+ }
193
+ };
194
+ const btnStyle = (active) => ({
195
+ padding: "4px 8px",
196
+ borderRadius: 3,
197
+ border: "none",
198
+ cursor: "pointer",
199
+ background: active ? "var(--nice-info, #339af0)" : "var(--nice-bg-secondary, #2c2e33)",
200
+ color: active ? "var(--nice-color-white, #fff)" : "var(--nice-text, #c1c2c5)",
201
+ fontSize: 11
202
+ });
203
+ const fieldStyle = {
204
+ width: "100%",
205
+ background: "var(--nice-bg-secondary, #2c2e33)",
206
+ color: "var(--nice-text, #c1c2c5)",
207
+ border: "1px solid var(--nice-border, #373a40)",
208
+ borderRadius: 3,
209
+ padding: "2px 4px",
210
+ fontSize: 10
211
+ };
212
+ const tabBtnStyle = (active) => ({
213
+ padding: "4px 12px",
214
+ border: "none",
215
+ borderBottom: active ? "2px solid var(--nice-info, #339af0)" : "2px solid transparent",
216
+ background: "transparent",
217
+ color: active ? "var(--nice-text-primary, #fff)" : "var(--nice-text-secondary, #868e96)",
218
+ cursor: "pointer",
219
+ fontSize: 11
220
+ });
221
+ return /* @__PURE__ */ jsxRuntime.jsxs(
222
+ "div",
223
+ {
224
+ className: `nice-font-editor ${className}`,
225
+ style: {
226
+ position: "relative",
227
+ display: "flex",
228
+ flexDirection: "column",
229
+ gap: 8,
230
+ background: "var(--nice-bg, #1a1b1e)",
231
+ color: "var(--nice-text, #c1c2c5)",
232
+ padding: 12,
233
+ borderRadius: 8
234
+ },
235
+ children: [
236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { position: "absolute", top: 6, right: 6, zIndex: 20 }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, FONT_EDITOR_TOUR) }) }),
237
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
238
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 14 }, children: t("fontEditor.fontEditor", "Font Editor") }),
239
+ /* @__PURE__ */ jsxRuntime.jsx(
240
+ "input",
241
+ {
242
+ value: editor.fontName,
243
+ onChange: (e) => editor.setFontName(e.target.value),
244
+ placeholder: t("fontEditor.fontName", "Font name"),
245
+ style: { flex: 1, maxWidth: 200, ...fieldStyle, fontSize: 12 }
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsxRuntime.jsx(
249
+ "button",
250
+ {
251
+ onClick: handleSave,
252
+ style: {
253
+ padding: "4px 12px",
254
+ background: "var(--nice-success, #2f9e44)",
255
+ color: "var(--nice-text-on-primary, #fff)",
256
+ border: "none",
257
+ borderRadius: 3,
258
+ cursor: "pointer",
259
+ fontSize: 11
260
+ },
261
+ children: t("fontEditor.save", "Save")
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsxRuntime.jsx(
265
+ "button",
266
+ {
267
+ onClick: handleExportSvg,
268
+ style: {
269
+ padding: "4px 12px",
270
+ background: "#228be6",
271
+ color: "var(--nice-color-white, #fff)",
272
+ border: "none",
273
+ borderRadius: 3,
274
+ cursor: "pointer",
275
+ fontSize: 11
276
+ },
277
+ children: t("fontEditor.exportSvgFont", "Export SVG Font")
278
+ }
279
+ )
280
+ ] }),
281
+ /* @__PURE__ */ jsxRuntime.jsxs(
282
+ "div",
283
+ {
284
+ style: { display: "flex", gap: 4, borderBottom: "1px solid var(--nice-border, #373a40)" },
285
+ children: [
286
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setTab("glyph"), style: tabBtnStyle(tab === "glyph"), children: t("fontEditor.glyphs", "Glyphs") }),
287
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setTab("metrics"), style: tabBtnStyle(tab === "metrics"), children: t("fontEditor.metrics", "Metrics") }),
288
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setTab("kerning"), style: tabBtnStyle(tab === "kerning"), children: t("fontEditor.kerning", "Kerning") })
289
+ ]
290
+ }
291
+ ),
292
+ tab === "glyph" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8 }, children: [
293
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [
294
+ /* @__PURE__ */ jsxRuntime.jsx(
295
+ "div",
296
+ {
297
+ style: {
298
+ fontSize: 11,
299
+ marginBottom: 4,
300
+ color: "var(--nice-text-secondary, #868e96)"
301
+ },
302
+ children: t("fontEditor.glyphs2", "{glyphs} glyphs").replace(
303
+ "{glyphs}",
304
+ String(editor.glyphs.length)
305
+ )
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsxRuntime.jsx(
309
+ "div",
310
+ {
311
+ style: {
312
+ display: "grid",
313
+ gridTemplateColumns: "repeat(auto-fill, 36px)",
314
+ gap: 2,
315
+ maxHeight: 300,
316
+ overflowY: "auto"
317
+ },
318
+ children: editor.glyphs.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
319
+ "div",
320
+ {
321
+ role: "button",
322
+ tabIndex: 0,
323
+ "aria-pressed": editor.selectedGlyph === g.unicode,
324
+ onKeyDown: uiCore.clickOnActivationKey,
325
+ onClick: () => {
326
+ editor.setSelectedGlyph(g.unicode);
327
+ setPathPoints([]);
328
+ },
329
+ title: `U+${g.unicode.toString(16).toUpperCase().padStart(4, "0")} - ${g.name}`,
330
+ style: {
331
+ width: 36,
332
+ height: 36,
333
+ display: "flex",
334
+ alignItems: "center",
335
+ justifyContent: "center",
336
+ borderRadius: 3,
337
+ cursor: "pointer",
338
+ fontSize: 16,
339
+ background: editor.selectedGlyph === g.unicode ? "var(--nice-info, #339af0)" : g.path ? "var(--nice-bg-secondary, #2c2e33)" : "#25262b",
340
+ color: editor.selectedGlyph === g.unicode ? "var(--nice-color-white, #fff)" : g.path ? "var(--nice-text, #c1c2c5)" : "#5c5f66",
341
+ border: g.path ? "1px solid var(--nice-border, #373a40)" : "1px solid transparent"
342
+ },
343
+ children: g.char
344
+ },
345
+ g.unicode
346
+ ))
347
+ }
348
+ )
349
+ ] }),
350
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 320, fontSize: 11 }, children: selectedG ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
351
+ /* @__PURE__ */ jsxRuntime.jsxs(
352
+ "div",
353
+ {
354
+ style: { display: "flex", justifyContent: "space-between", alignItems: "center" },
355
+ children: [
356
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
357
+ '"',
358
+ selectedG.char,
359
+ '" (U+',
360
+ selectedG.unicode.toString(16).toUpperCase().padStart(4, "0"),
361
+ ")"
362
+ ] }),
363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 2 }, children: [
364
+ /* @__PURE__ */ jsxRuntime.jsx(
365
+ "button",
366
+ {
367
+ onClick: () => setDrawMode("select"),
368
+ style: btnStyle(drawMode === "select"),
369
+ children: t("fontEditor.select", "Select")
370
+ }
371
+ ),
372
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setDrawMode("pen"), style: btnStyle(drawMode === "pen"), children: t("fontEditor.pen", "Pen") }),
373
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: clearPath, style: btnStyle(false), children: t("fontEditor.clear", "Clear") }),
374
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setShowGrid((g) => !g), style: btnStyle(showGrid), children: t("fontEditor.grid", "Grid") })
375
+ ] })
376
+ ]
377
+ }
378
+ ),
379
+ /* @__PURE__ */ jsxRuntime.jsxs(
380
+ "svg",
381
+ {
382
+ width: 280,
383
+ height: 280,
384
+ viewBox: `0 0 ${em} ${em}`,
385
+ style: {
386
+ background: "var(--nice-bg, #141517)",
387
+ borderRadius: 4,
388
+ cursor: drawMode === "pen" ? "crosshair" : "default"
389
+ },
390
+ onClick: handleGlyphCanvasClick,
391
+ children: [
392
+ showGrid && Array.from({ length: 11 }, (_, i) => {
393
+ const pos = i / 10 * em;
394
+ return /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
395
+ /* @__PURE__ */ jsxRuntime.jsx(
396
+ "line",
397
+ {
398
+ x1: pos,
399
+ y1: 0,
400
+ x2: pos,
401
+ y2: em,
402
+ stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))",
403
+ strokeWidth: em * 2e-3
404
+ }
405
+ ),
406
+ /* @__PURE__ */ jsxRuntime.jsx(
407
+ "line",
408
+ {
409
+ x1: 0,
410
+ y1: pos,
411
+ x2: em,
412
+ y2: pos,
413
+ stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))",
414
+ strokeWidth: em * 2e-3
415
+ }
416
+ )
417
+ ] }, i);
418
+ }),
419
+ /* @__PURE__ */ jsxRuntime.jsx(
420
+ "line",
421
+ {
422
+ x1: 0,
423
+ y1: em - editor.metrics.ascent,
424
+ x2: em,
425
+ y2: em - editor.metrics.ascent,
426
+ stroke: "rgba(51,154,240,0.3)",
427
+ strokeWidth: em * 3e-3,
428
+ strokeDasharray: `${em * 0.01}`
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsxRuntime.jsx(
432
+ "line",
433
+ {
434
+ x1: 0,
435
+ y1: em,
436
+ x2: em,
437
+ y2: em,
438
+ stroke: "rgba(47,158,68,0.3)",
439
+ strokeWidth: em * 3e-3
440
+ }
441
+ ),
442
+ /* @__PURE__ */ jsxRuntime.jsx(
443
+ "line",
444
+ {
445
+ x1: 0,
446
+ y1: em - editor.metrics.xHeight,
447
+ x2: em,
448
+ y2: em - editor.metrics.xHeight,
449
+ stroke: "rgba(249,226,175,0.3)",
450
+ strokeWidth: em * 3e-3,
451
+ strokeDasharray: `${em * 0.01}`
452
+ }
453
+ ),
454
+ /* @__PURE__ */ jsxRuntime.jsx(
455
+ "line",
456
+ {
457
+ x1: 0,
458
+ y1: em - editor.metrics.capHeight,
459
+ x2: em,
460
+ y2: em - editor.metrics.capHeight,
461
+ stroke: "rgba(243,139,168,0.3)",
462
+ strokeWidth: em * 3e-3,
463
+ strokeDasharray: `${em * 0.01}`
464
+ }
465
+ ),
466
+ /* @__PURE__ */ jsxRuntime.jsx(
467
+ "line",
468
+ {
469
+ x1: selectedG.leftBearing,
470
+ y1: 0,
471
+ x2: selectedG.leftBearing,
472
+ y2: em,
473
+ stroke: "rgba(166,227,161,0.2)",
474
+ strokeWidth: em * 2e-3
475
+ }
476
+ ),
477
+ /* @__PURE__ */ jsxRuntime.jsx(
478
+ "line",
479
+ {
480
+ x1: selectedG.advanceWidth,
481
+ y1: 0,
482
+ x2: selectedG.advanceWidth,
483
+ y2: em,
484
+ stroke: "rgba(243,139,168,0.2)",
485
+ strokeWidth: em * 2e-3
486
+ }
487
+ ),
488
+ selectedG.path && /* @__PURE__ */ jsxRuntime.jsx(
489
+ "path",
490
+ {
491
+ d: selectedG.path,
492
+ fill: "rgba(205,214,244,0.8)",
493
+ stroke: "#89b4fa",
494
+ strokeWidth: em * 5e-3
495
+ }
496
+ ),
497
+ pathPoints.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(
498
+ "circle",
499
+ {
500
+ cx: p.x,
501
+ cy: p.y,
502
+ r: em * 0.015,
503
+ fill: i === 0 ? "var(--nice-success, #2f9e44)" : "var(--nice-info, #339af0)",
504
+ stroke: "var(--nice-bg, #fff)",
505
+ strokeWidth: em * 3e-3
506
+ },
507
+ i
508
+ )),
509
+ /* @__PURE__ */ jsxRuntime.jsx(
510
+ "text",
511
+ {
512
+ x: em * 0.02,
513
+ y: em - editor.metrics.ascent - em * 0.01,
514
+ fill: "rgba(51,154,240,0.5)",
515
+ fontSize: em * 0.03,
516
+ children: t("fontEditor.ascent2", "ascent")
517
+ }
518
+ ),
519
+ /* @__PURE__ */ jsxRuntime.jsx(
520
+ "text",
521
+ {
522
+ x: em * 0.02,
523
+ y: em - editor.metrics.xHeight - em * 0.01,
524
+ fill: "rgba(249,226,175,0.5)",
525
+ fontSize: em * 0.03,
526
+ children: t("fontEditor.xHeight2", "x-height")
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsxRuntime.jsx(
530
+ "text",
531
+ {
532
+ x: em * 0.02,
533
+ y: em - editor.metrics.capHeight - em * 0.01,
534
+ fill: "rgba(243,139,168,0.5)",
535
+ fontSize: em * 0.03,
536
+ children: t("fontEditor.cap2", "cap")
537
+ }
538
+ )
539
+ ]
540
+ }
541
+ ),
542
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
543
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("fontEditor.svgPath", "SVG Path") }),
544
+ /* @__PURE__ */ jsxRuntime.jsx(
545
+ "textarea",
546
+ {
547
+ value: selectedG.path,
548
+ onChange: (e) => editor.updateGlyph(selectedG.unicode, { path: e.target.value }),
549
+ rows: 3,
550
+ style: { ...fieldStyle, fontFamily: "monospace", resize: "vertical" }
551
+ }
552
+ )
553
+ ] }),
554
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }, children: [
555
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("fontEditor.advanceWidth", "Advance Width") }),
556
+ /* @__PURE__ */ jsxRuntime.jsx(
557
+ "input",
558
+ {
559
+ type: "number",
560
+ value: selectedG.advanceWidth,
561
+ onChange: (e) => editor.updateGlyph(selectedG.unicode, {
562
+ advanceWidth: Number(e.target.value)
563
+ }),
564
+ style: fieldStyle
565
+ }
566
+ ),
567
+ /* @__PURE__ */ jsxRuntime.jsx("label", { children: t("fontEditor.leftBearing", "Left Bearing") }),
568
+ /* @__PURE__ */ jsxRuntime.jsx(
569
+ "input",
570
+ {
571
+ type: "number",
572
+ value: selectedG.leftBearing,
573
+ onChange: (e) => editor.updateGlyph(selectedG.unicode, { leftBearing: Number(e.target.value) }),
574
+ style: fieldStyle
575
+ }
576
+ )
577
+ ] })
578
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
579
+ "div",
101
580
  {
102
- unicode,
103
- char,
104
- name: `uni${unicode.toString(16).toUpperCase().padStart(4, '0')}`,
105
- path: '',
106
- advanceWidth: 500,
107
- leftBearing: 50,
581
+ style: {
582
+ color: "var(--nice-text-secondary, #868e96)",
583
+ padding: 20,
584
+ textAlign: "center"
585
+ },
586
+ children: t("fontEditor.selectAGlyphToEdit", "Select a glyph to edit")
587
+ }
588
+ ) })
589
+ ] }),
590
+ tab === "metrics" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { maxWidth: 400 }, children: [
591
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12, display: "block", marginBottom: 8 }, children: t("fontEditor.fontMetrics", "Font Metrics") }),
592
+ [
593
+ ["unitsPerEm", "Units per Em"],
594
+ ["ascent", "Ascent"],
595
+ ["descent", "Descent"],
596
+ ["lineGap", "Line Gap"],
597
+ ["xHeight", "x-Height"],
598
+ ["capHeight", "Cap Height"]
599
+ ].map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs(
600
+ "div",
601
+ {
602
+ style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 4 },
603
+ children: [
604
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { minWidth: 100, fontSize: 11 }, children: label }),
605
+ /* @__PURE__ */ jsxRuntime.jsx(
606
+ "input",
607
+ {
608
+ type: "number",
609
+ value: editor.metrics[key],
610
+ onChange: (e) => editor.setMetrics({ ...editor.metrics, [key]: Number(e.target.value) }),
611
+ style: fieldStyle
612
+ }
613
+ )
614
+ ]
108
615
  },
109
- ];
110
- setGlyphs(newGlyphs);
111
- pushHistory(newGlyphs);
112
- }, [glyphs, pushHistory]);
113
- const toFontData = React.useCallback(() => ({
114
- name: fontName,
115
- family: fontName.replace(/\s+/g, ''),
116
- metrics,
117
- glyphs,
118
- }), [fontName, metrics, glyphs]);
119
- return {
120
- fontName,
121
- setFontName,
122
- metrics,
123
- setMetrics,
124
- glyphs,
125
- selectedGlyph,
126
- setSelectedGlyph,
127
- updateGlyph,
128
- addGlyph,
129
- previewText,
130
- setPreviewText,
131
- toFontData,
132
- };
133
- }
134
- // ── Component ───────────────────────────────────────────────────
135
- /** Font glyph editor with metrics, visual path editing, and preview. */
136
- const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial, }) => {
137
- var _a;
138
- const { t } = i18n.useNiceTranslation();
139
- const editor = useFontEditor(fontName);
140
- const [drawMode, setDrawMode] = React.useState('select');
141
- const [pathPoints, setPathPoints] = React.useState([]);
142
- const [tab, setTab] = React.useState('glyph');
143
- const [kerningPairs, setKerningPairs] = React.useState([]);
144
- const [newKernLeft, setNewKernLeft] = React.useState('A');
145
- const [newKernRight, setNewKernRight] = React.useState('V');
146
- const [showGrid, setShowGrid] = React.useState(true);
147
- const handleSave = () => onSave === null || onSave === void 0 ? void 0 : onSave(editor.toFontData());
148
- const handleExportSvg = () => {
149
- const data = editor.toFontData();
150
- const svg = exportToSvgFont(data);
151
- const blob = new Blob([svg], { type: 'image/svg+xml' });
152
- const url = URL.createObjectURL(blob);
153
- const a = document.createElement('a');
154
- a.href = url;
155
- a.download = `${data.family}.svg`;
156
- a.click();
157
- URL.revokeObjectURL(url);
158
- };
159
- const selectedG = editor.selectedGlyph !== null
160
- ? ((_a = editor.glyphs.find((g) => g.unicode === editor.selectedGlyph)) !== null && _a !== void 0 ? _a : null)
161
- : null;
162
- const em = editor.metrics.unitsPerEm;
163
- // Click on glyph SVG to add path control points
164
- const handleGlyphCanvasClick = (e) => {
165
- if (drawMode !== 'pen' || !selectedG) {
166
- return;
167
- }
168
- const svg = e.currentTarget;
169
- const rect = svg.getBoundingClientRect();
170
- const x = Math.round(((e.clientX - rect.left) / rect.width) * em);
171
- const y = Math.round(((e.clientY - rect.top) / rect.height) * em);
172
- const newPoints = [...pathPoints, { x, y }];
173
- setPathPoints(newPoints);
174
- // Build SVG path from points
175
- if (newPoints.length >= 2) {
176
- let d = `M ${newPoints[0].x} ${newPoints[0].y}`;
177
- for (let i = 1; i < newPoints.length; i++) {
178
- d += ` L ${newPoints[i].x} ${newPoints[i].y}`;
616
+ key
617
+ )),
618
+ /* @__PURE__ */ jsxRuntime.jsxs(
619
+ "svg",
620
+ {
621
+ width: 300,
622
+ height: 200,
623
+ viewBox: `0 0 ${em} ${em}`,
624
+ style: { background: "var(--nice-bg, #141517)", borderRadius: 4, marginTop: 12 },
625
+ children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx(
627
+ "rect",
628
+ {
629
+ x: 0,
630
+ y: em - editor.metrics.ascent,
631
+ width: em,
632
+ height: editor.metrics.ascent - editor.metrics.descent,
633
+ fill: "rgba(51,154,240,0.05)",
634
+ stroke: "rgba(51,154,240,0.2)",
635
+ strokeWidth: em * 3e-3
636
+ }
637
+ ),
638
+ /* @__PURE__ */ jsxRuntime.jsx(
639
+ "line",
640
+ {
641
+ x1: 0,
642
+ y1: em - editor.metrics.ascent,
643
+ x2: em,
644
+ y2: em - editor.metrics.ascent,
645
+ stroke: "var(--nice-info, #339af0)",
646
+ strokeWidth: em * 4e-3
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsxRuntime.jsx(
650
+ "line",
651
+ {
652
+ x1: 0,
653
+ y1: em,
654
+ x2: em,
655
+ y2: em,
656
+ stroke: "var(--nice-success, #2f9e44)",
657
+ strokeWidth: em * 4e-3
658
+ }
659
+ ),
660
+ /* @__PURE__ */ jsxRuntime.jsx(
661
+ "line",
662
+ {
663
+ x1: 0,
664
+ y1: em + Math.abs(editor.metrics.descent),
665
+ x2: em,
666
+ y2: em + Math.abs(editor.metrics.descent),
667
+ stroke: "#e03131",
668
+ strokeWidth: em * 4e-3
669
+ }
670
+ ),
671
+ /* @__PURE__ */ jsxRuntime.jsx(
672
+ "line",
673
+ {
674
+ x1: 0,
675
+ y1: em - editor.metrics.xHeight,
676
+ x2: em,
677
+ y2: em - editor.metrics.xHeight,
678
+ stroke: "#f9e2af",
679
+ strokeWidth: em * 3e-3,
680
+ strokeDasharray: `${em * 0.015}`
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsxRuntime.jsx(
684
+ "line",
685
+ {
686
+ x1: 0,
687
+ y1: em - editor.metrics.capHeight,
688
+ x2: em,
689
+ y2: em - editor.metrics.capHeight,
690
+ stroke: "#f38ba8",
691
+ strokeWidth: em * 3e-3,
692
+ strokeDasharray: `${em * 0.015}`
693
+ }
694
+ ),
695
+ /* @__PURE__ */ jsxRuntime.jsx(
696
+ "text",
697
+ {
698
+ x: em * 0.02,
699
+ y: em - editor.metrics.ascent + em * 0.05,
700
+ fill: "var(--nice-info, #339af0)",
701
+ fontSize: em * 0.04,
702
+ children: t("fontEditor.ascent", "Ascent ({ascent})").replace(
703
+ "{ascent}",
704
+ String(editor.metrics.ascent)
705
+ )
706
+ }
707
+ ),
708
+ /* @__PURE__ */ jsxRuntime.jsx(
709
+ "text",
710
+ {
711
+ x: em * 0.02,
712
+ y: em + em * 0.05,
713
+ fill: "var(--nice-success, #2f9e44)",
714
+ fontSize: em * 0.04,
715
+ children: t("fontEditor.baseline", "Baseline")
716
+ }
717
+ ),
718
+ /* @__PURE__ */ jsxRuntime.jsx(
719
+ "text",
720
+ {
721
+ x: em * 0.02,
722
+ y: em - editor.metrics.xHeight + em * 0.05,
723
+ fill: "#f9e2af",
724
+ fontSize: em * 0.04,
725
+ children: t("fontEditor.xHeight", "x-Height ({xHeight})").replace(
726
+ "{xHeight}",
727
+ String(editor.metrics.xHeight)
728
+ )
729
+ }
730
+ ),
731
+ /* @__PURE__ */ jsxRuntime.jsx(
732
+ "text",
733
+ {
734
+ x: em * 0.02,
735
+ y: em - editor.metrics.capHeight + em * 0.05,
736
+ fill: "#f38ba8",
737
+ fontSize: em * 0.04,
738
+ children: t("fontEditor.cap", "Cap ({capHeight})").replace(
739
+ "{capHeight}",
740
+ String(editor.metrics.capHeight)
741
+ )
742
+ }
743
+ )
744
+ ]
179
745
  }
180
- d += ' Z';
181
- editor.updateGlyph(selectedG.unicode, { path: d });
182
- }
183
- };
184
- const clearPath = () => {
185
- setPathPoints([]);
186
- if (selectedG) {
187
- editor.updateGlyph(selectedG.unicode, { path: '' });
188
- }
189
- };
190
- const btnStyle = (active) => ({
191
- padding: '4px 8px',
192
- borderRadius: 3,
193
- border: 'none',
194
- cursor: 'pointer',
195
- background: active ? 'var(--nice-info, #339af0)' : 'var(--nice-bg-secondary, #2c2e33)',
196
- color: active ? 'var(--nice-color-white, #fff)' : 'var(--nice-text, #c1c2c5)',
197
- fontSize: 11,
198
- });
199
- const fieldStyle = {
200
- width: '100%',
201
- background: 'var(--nice-bg-secondary, #2c2e33)',
202
- color: 'var(--nice-text, #c1c2c5)',
203
- border: '1px solid var(--nice-border, #373a40)',
204
- borderRadius: 3,
205
- padding: '2px 4px',
206
- fontSize: 10,
207
- };
208
- const tabBtnStyle = (active) => ({
209
- padding: '4px 12px',
210
- border: 'none',
211
- borderBottom: active ? '2px solid var(--nice-info, #339af0)' : '2px solid transparent',
212
- background: 'transparent',
213
- color: active ? 'var(--nice-text-primary, #fff)' : 'var(--nice-text-secondary, #868e96)',
214
- cursor: 'pointer',
215
- fontSize: 11,
216
- });
217
- return (jsxRuntime.jsxs("div", { className: `nice-font-editor ${className}`, style: {
218
- position: 'relative',
219
- display: 'flex',
220
- flexDirection: 'column',
221
- gap: 8,
222
- background: 'var(--nice-bg, #1a1b1e)',
223
- color: 'var(--nice-text, #c1c2c5)',
224
- padding: 12,
225
- borderRadius: 8,
226
- }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, FONT_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 14 }, children: t('fontEditor.fontEditor', 'Font Editor') }), jsxRuntime.jsx("input", { value: editor.fontName, onChange: (e) => editor.setFontName(e.target.value), placeholder: t('fontEditor.fontName', 'Font name'), style: { flex: 1, maxWidth: 200, ...fieldStyle, fontSize: 12 } }), jsxRuntime.jsx("button", { onClick: handleSave, style: {
227
- padding: '4px 12px',
228
- background: 'var(--nice-success, #2f9e44)',
229
- color: 'var(--nice-text-on-primary, #fff)',
230
- border: 'none',
231
- borderRadius: 3,
232
- cursor: 'pointer',
233
- fontSize: 11,
234
- }, children: t('fontEditor.save', 'Save') }), jsxRuntime.jsx("button", { onClick: handleExportSvg, style: {
235
- padding: '4px 12px',
236
- background: '#228be6',
237
- color: 'var(--nice-color-white, #fff)',
238
- border: 'none',
239
- borderRadius: 3,
240
- cursor: 'pointer',
241
- fontSize: 11,
242
- }, children: t('fontEditor.exportSvgFont', 'Export SVG Font') })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, borderBottom: '1px solid var(--nice-border, #373a40)' }, children: [jsxRuntime.jsx("button", { onClick: () => setTab('glyph'), style: tabBtnStyle(tab === 'glyph'), children: t('fontEditor.glyphs', 'Glyphs') }), jsxRuntime.jsx("button", { onClick: () => setTab('metrics'), style: tabBtnStyle(tab === 'metrics'), children: t('fontEditor.metrics', 'Metrics') }), jsxRuntime.jsx("button", { onClick: () => setTab('kerning'), style: tabBtnStyle(tab === 'kerning'), children: t('fontEditor.kerning', 'Kerning') })] }), tab === 'glyph' && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("div", { style: {
243
- fontSize: 11,
244
- marginBottom: 4,
245
- color: 'var(--nice-text-secondary, #868e96)',
246
- }, children: t('fontEditor.glyphs2', '{glyphs} glyphs').replace('{glyphs}', String(editor.glyphs.length)) }), jsxRuntime.jsx("div", { style: {
247
- display: 'grid',
248
- gridTemplateColumns: 'repeat(auto-fill, 36px)',
249
- gap: 2,
250
- maxHeight: 300,
251
- overflowY: 'auto',
252
- }, children: editor.glyphs.map((g) => (jsxRuntime.jsx("div", { role: "button", tabIndex: 0, "aria-pressed": editor.selectedGlyph === g.unicode, onKeyDown: uiCore.clickOnActivationKey, onClick: () => {
253
- editor.setSelectedGlyph(g.unicode);
254
- setPathPoints([]);
255
- }, title: `U+${g.unicode.toString(16).toUpperCase().padStart(4, '0')} - ${g.name}`, style: {
256
- width: 36,
257
- height: 36,
258
- display: 'flex',
259
- alignItems: 'center',
260
- justifyContent: 'center',
261
- borderRadius: 3,
262
- cursor: 'pointer',
263
- fontSize: 16,
264
- background: editor.selectedGlyph === g.unicode
265
- ? 'var(--nice-info, #339af0)'
266
- : g.path
267
- ? 'var(--nice-bg-secondary, #2c2e33)'
268
- : '#25262b',
269
- color: editor.selectedGlyph === g.unicode
270
- ? 'var(--nice-color-white, #fff)'
271
- : g.path
272
- ? 'var(--nice-text, #c1c2c5)'
273
- : '#5c5f66',
274
- border: g.path
275
- ? '1px solid var(--nice-border, #373a40)'
276
- : '1px solid transparent',
277
- }, children: g.char }, g.unicode))) })] }), jsxRuntime.jsx("div", { style: { width: 320, fontSize: 11 }, children: selectedG ? (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: ["\"", selectedG.char, "\" (U+", selectedG.unicode.toString(16).toUpperCase().padStart(4, '0'), ")"] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsxRuntime.jsx("button", { onClick: () => setDrawMode('select'), style: btnStyle(drawMode === 'select'), children: t('fontEditor.select', 'Select') }), jsxRuntime.jsx("button", { onClick: () => setDrawMode('pen'), style: btnStyle(drawMode === 'pen'), children: t('fontEditor.pen', 'Pen') }), jsxRuntime.jsx("button", { onClick: clearPath, style: btnStyle(false), children: t('fontEditor.clear', 'Clear') }), jsxRuntime.jsx("button", { onClick: () => setShowGrid((g) => !g), style: btnStyle(showGrid), children: t('fontEditor.grid', 'Grid') })] })] }), jsxRuntime.jsxs("svg", { width: 280, height: 280, viewBox: `0 0 ${em} ${em}`, style: {
278
- background: 'var(--nice-bg, #141517)',
279
- borderRadius: 4,
280
- cursor: drawMode === 'pen' ? 'crosshair' : 'default',
281
- }, onClick: handleGlyphCanvasClick, children: [showGrid &&
282
- Array.from({ length: 11 }, (_, i) => {
283
- const pos = (i / 10) * em;
284
- return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("line", { x1: pos, y1: 0, x2: pos, y2: em, stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))", strokeWidth: em * 0.002 }), jsxRuntime.jsx("line", { x1: 0, y1: pos, x2: em, y2: pos, stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))", strokeWidth: em * 0.002 })] }, i));
285
- }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "rgba(51,154,240,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "rgba(47,158,68,0.3)", strokeWidth: em * 0.003 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "rgba(249,226,175,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "rgba(243,139,168,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: selectedG.leftBearing, y1: 0, x2: selectedG.leftBearing, y2: em, stroke: "rgba(166,227,161,0.2)", strokeWidth: em * 0.002 }), jsxRuntime.jsx("line", { x1: selectedG.advanceWidth, y1: 0, x2: selectedG.advanceWidth, y2: em, stroke: "rgba(243,139,168,0.2)", strokeWidth: em * 0.002 }), selectedG.path && (jsxRuntime.jsx("path", { d: selectedG.path, fill: "rgba(205,214,244,0.8)", stroke: "#89b4fa", strokeWidth: em * 0.005 })), pathPoints.map((p, i) => (jsxRuntime.jsx("circle", { cx: p.x, cy: p.y, r: em * 0.015, fill: i === 0 ? 'var(--nice-success, #2f9e44)' : 'var(--nice-info, #339af0)', stroke: "var(--nice-bg, #fff)", strokeWidth: em * 0.003 }, i))), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent - em * 0.01, fill: "rgba(51,154,240,0.5)", fontSize: em * 0.03, children: t('fontEditor.ascent2', 'ascent') }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.xHeight - em * 0.01, fill: "rgba(249,226,175,0.5)", fontSize: em * 0.03, children: t('fontEditor.xHeight2', 'x-height') }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.capHeight - em * 0.01, fill: "rgba(243,139,168,0.5)", fontSize: em * 0.03, children: t('fontEditor.cap2', 'cap') })] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { children: t('fontEditor.svgPath', 'SVG Path') }), jsxRuntime.jsx("textarea", { value: selectedG.path, onChange: (e) => editor.updateGlyph(selectedG.unicode, { path: e.target.value }), rows: 3, style: { ...fieldStyle, fontFamily: 'monospace', resize: 'vertical' } })] }), jsxRuntime.jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [jsxRuntime.jsx("label", { children: t('fontEditor.advanceWidth', 'Advance Width') }), jsxRuntime.jsx("input", { type: "number", value: selectedG.advanceWidth, onChange: (e) => editor.updateGlyph(selectedG.unicode, {
286
- advanceWidth: Number(e.target.value),
287
- }), style: fieldStyle }), jsxRuntime.jsx("label", { children: t('fontEditor.leftBearing', 'Left Bearing') }), jsxRuntime.jsx("input", { type: "number", value: selectedG.leftBearing, onChange: (e) => editor.updateGlyph(selectedG.unicode, { leftBearing: Number(e.target.value) }), style: fieldStyle })] })] })) : (jsxRuntime.jsx("div", { style: {
288
- color: 'var(--nice-text-secondary, #868e96)',
289
- padding: 20,
290
- textAlign: 'center',
291
- }, children: t('fontEditor.selectAGlyphToEdit', 'Select a glyph to edit') })) })] })), tab === 'metrics' && (jsxRuntime.jsxs("div", { style: { maxWidth: 400 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: t('fontEditor.fontMetrics', 'Font Metrics') }), [
292
- ['unitsPerEm', 'Units per Em'],
293
- ['ascent', 'Ascent'],
294
- ['descent', 'Descent'],
295
- ['lineGap', 'Line Gap'],
296
- ['xHeight', 'x-Height'],
297
- ['capHeight', 'Cap Height'],
298
- ].map(([key, label]) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }, children: [jsxRuntime.jsx("span", { style: { minWidth: 100, fontSize: 11 }, children: label }), jsxRuntime.jsx("input", { type: "number", value: editor.metrics[key], onChange: (e) => editor.setMetrics({ ...editor.metrics, [key]: Number(e.target.value) }), style: fieldStyle })] }, key))), jsxRuntime.jsxs("svg", { width: 300, height: 200, viewBox: `0 0 ${em} ${em}`, style: { background: 'var(--nice-bg, #141517)', borderRadius: 4, marginTop: 12 }, children: [jsxRuntime.jsx("rect", { x: 0, y: em - editor.metrics.ascent, width: em, height: editor.metrics.ascent - editor.metrics.descent, fill: "rgba(51,154,240,0.05)", stroke: "rgba(51,154,240,0.2)", strokeWidth: em * 0.003 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "var(--nice-info, #339af0)", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "var(--nice-success, #2f9e44)", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em + Math.abs(editor.metrics.descent), x2: em, y2: em + Math.abs(editor.metrics.descent), stroke: "#e03131", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "#f9e2af", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "#f38ba8", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent + em * 0.05, fill: "var(--nice-info, #339af0)", fontSize: em * 0.04, children: t('fontEditor.ascent', 'Ascent ({ascent})').replace('{ascent}', String(editor.metrics.ascent)) }), jsxRuntime.jsx("text", { x: em * 0.02, y: em + em * 0.05, fill: "var(--nice-success, #2f9e44)", fontSize: em * 0.04, children: t('fontEditor.baseline', 'Baseline') }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.xHeight + em * 0.05, fill: "#f9e2af", fontSize: em * 0.04, children: t('fontEditor.xHeight', 'x-Height ({xHeight})').replace('{xHeight}', String(editor.metrics.xHeight)) }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.capHeight + em * 0.05, fill: "#f38ba8", fontSize: em * 0.04, children: t('fontEditor.cap', 'Cap ({capHeight})').replace('{capHeight}', String(editor.metrics.capHeight)) })] })] })), tab === 'kerning' && (jsxRuntime.jsxs("div", { style: { maxWidth: 500 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: t('fontEditor.kerningPairs', 'Kerning Pairs') }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, marginBottom: 8, alignItems: 'center', fontSize: 11 }, children: [jsxRuntime.jsx("input", { value: newKernLeft, onChange: (e) => setNewKernLeft(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsxRuntime.jsx("input", { value: newKernRight, onChange: (e) => setNewKernRight(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsxRuntime.jsx("button", { onClick: () => {
299
- if (newKernLeft && newKernRight) {
300
- setKerningPairs((prev) => [
301
- ...prev,
302
- { left: newKernLeft, right: newKernRight, value: -50 },
303
- ]);
304
- }
305
- }, style: btnStyle(false), children: t('fontEditor.addPair', 'Add Pair') })] }), jsxRuntime.jsx("div", { style: { maxHeight: 200, overflowY: 'auto' }, children: kerningPairs.length === 0 ? (jsxRuntime.jsx("div", { style: {
306
- color: 'var(--nice-text-secondary, #868e96)',
307
- fontSize: 11,
308
- padding: 8,
309
- textAlign: 'center',
310
- }, children: t('fontEditor.noKerningPairsDefined', 'No kerning pairs defined') })) : (kerningPairs.map((kp, i) => (jsxRuntime.jsxs("div", { style: {
311
- display: 'flex',
312
- gap: 8,
313
- alignItems: 'center',
314
- padding: '2px 0',
315
- fontSize: 11,
316
- }, children: [jsxRuntime.jsxs("span", { style: { minWidth: 40, textAlign: 'center', fontSize: 16 }, children: [kp.left, kp.right] }), jsxRuntime.jsx("input", { type: "range", min: -200, max: 200, value: kp.value, onChange: (e) => setKerningPairs((prev) => prev.map((p, j) => (j === i ? { ...p, value: Number(e.target.value) } : p))), style: { flex: 1 } }), jsxRuntime.jsx("span", { style: { minWidth: 40, textAlign: 'right' }, children: kp.value }), jsxRuntime.jsx("button", { onClick: () => setKerningPairs((prev) => prev.filter((_, j) => j !== i)), style: {
317
- background: 'transparent',
318
- border: 'none',
319
- color: 'var(--nice-danger, #ff6b6b)',
320
- cursor: 'pointer',
321
- }, "aria-label": t('fontEditor.removeKerningPair', 'Remove kerning pair'), title: t('fontEditor.removeKerningPair', 'Remove kerning pair'), children: jsxRuntime.jsx(uiCore.NiceIcon, { name: "close", size: 14 }) })] }, i)))) })] })), jsxRuntime.jsxs("div", { style: { marginTop: 8, borderTop: '1px solid var(--nice-border, #373a40)', paddingTop: 8 }, children: [jsxRuntime.jsx("div", { style: { fontSize: 11, color: 'var(--nice-text-secondary, #868e96)', marginBottom: 4 }, children: t('fontEditor.preview', 'Preview') }), jsxRuntime.jsx("input", { value: editor.previewText, onChange: (e) => editor.setPreviewText(e.target.value), style: { width: '100%', padding: '6px 8px', ...fieldStyle, fontSize: 12 } }), jsxRuntime.jsx("div", { style: {
322
- marginTop: 8,
323
- padding: 16,
324
- background: 'var(--nice-bg, #fff)',
325
- color: 'var(--nice-text-primary, #212529)',
326
- borderRadius: 6,
327
- fontSize: 24,
328
- fontFamily: 'serif',
329
- }, children: editor.previewText })] })] }));
746
+ )
747
+ ] }),
748
+ tab === "kerning" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { maxWidth: 500 }, children: [
749
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12, display: "block", marginBottom: 8 }, children: t("fontEditor.kerningPairs", "Kerning Pairs") }),
750
+ /* @__PURE__ */ jsxRuntime.jsxs(
751
+ "div",
752
+ {
753
+ style: { display: "flex", gap: 4, marginBottom: 8, alignItems: "center", fontSize: 11 },
754
+ children: [
755
+ /* @__PURE__ */ jsxRuntime.jsx(
756
+ "input",
757
+ {
758
+ value: newKernLeft,
759
+ onChange: (e) => setNewKernLeft(e.target.value),
760
+ maxLength: 1,
761
+ style: { ...fieldStyle, width: 30, textAlign: "center" }
762
+ }
763
+ ),
764
+ /* @__PURE__ */ jsxRuntime.jsx(
765
+ "input",
766
+ {
767
+ value: newKernRight,
768
+ onChange: (e) => setNewKernRight(e.target.value),
769
+ maxLength: 1,
770
+ style: { ...fieldStyle, width: 30, textAlign: "center" }
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsxRuntime.jsx(
774
+ "button",
775
+ {
776
+ onClick: () => {
777
+ if (newKernLeft && newKernRight) {
778
+ setKerningPairs((prev) => [
779
+ ...prev,
780
+ { left: newKernLeft, right: newKernRight, value: -50 }
781
+ ]);
782
+ }
783
+ },
784
+ style: btnStyle(false),
785
+ children: t("fontEditor.addPair", "Add Pair")
786
+ }
787
+ )
788
+ ]
789
+ }
790
+ ),
791
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { maxHeight: 200, overflowY: "auto" }, children: kerningPairs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
792
+ "div",
793
+ {
794
+ style: {
795
+ color: "var(--nice-text-secondary, #868e96)",
796
+ fontSize: 11,
797
+ padding: 8,
798
+ textAlign: "center"
799
+ },
800
+ children: t("fontEditor.noKerningPairsDefined", "No kerning pairs defined")
801
+ }
802
+ ) : kerningPairs.map((kp, i) => /* @__PURE__ */ jsxRuntime.jsxs(
803
+ "div",
804
+ {
805
+ style: {
806
+ display: "flex",
807
+ gap: 8,
808
+ alignItems: "center",
809
+ padding: "2px 0",
810
+ fontSize: 11
811
+ },
812
+ children: [
813
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { minWidth: 40, textAlign: "center", fontSize: 16 }, children: [
814
+ kp.left,
815
+ kp.right
816
+ ] }),
817
+ /* @__PURE__ */ jsxRuntime.jsx(
818
+ "input",
819
+ {
820
+ type: "range",
821
+ min: -200,
822
+ max: 200,
823
+ value: kp.value,
824
+ onChange: (e) => setKerningPairs(
825
+ (prev) => prev.map((p, j) => j === i ? { ...p, value: Number(e.target.value) } : p)
826
+ ),
827
+ style: { flex: 1 }
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { minWidth: 40, textAlign: "right" }, children: kp.value }),
831
+ /* @__PURE__ */ jsxRuntime.jsx(
832
+ "button",
833
+ {
834
+ onClick: () => setKerningPairs((prev) => prev.filter((_, j) => j !== i)),
835
+ style: {
836
+ background: "transparent",
837
+ border: "none",
838
+ color: "var(--nice-danger, #ff6b6b)",
839
+ cursor: "pointer"
840
+ },
841
+ "aria-label": t("fontEditor.removeKerningPair", "Remove kerning pair"),
842
+ title: t("fontEditor.removeKerningPair", "Remove kerning pair"),
843
+ children: /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "close", size: 14 })
844
+ }
845
+ )
846
+ ]
847
+ },
848
+ i
849
+ )) })
850
+ ] }),
851
+ /* @__PURE__ */ jsxRuntime.jsxs(
852
+ "div",
853
+ {
854
+ style: { marginTop: 8, borderTop: "1px solid var(--nice-border, #373a40)", paddingTop: 8 },
855
+ children: [
856
+ /* @__PURE__ */ jsxRuntime.jsx(
857
+ "div",
858
+ {
859
+ style: { fontSize: 11, color: "var(--nice-text-secondary, #868e96)", marginBottom: 4 },
860
+ children: t("fontEditor.preview", "Preview")
861
+ }
862
+ ),
863
+ /* @__PURE__ */ jsxRuntime.jsx(
864
+ "input",
865
+ {
866
+ value: editor.previewText,
867
+ onChange: (e) => editor.setPreviewText(e.target.value),
868
+ style: { width: "100%", padding: "6px 8px", ...fieldStyle, fontSize: 12 }
869
+ }
870
+ ),
871
+ /* @__PURE__ */ jsxRuntime.jsx(
872
+ "div",
873
+ {
874
+ style: {
875
+ marginTop: 8,
876
+ padding: 16,
877
+ background: "var(--nice-bg, #fff)",
878
+ color: "var(--nice-text-primary, #212529)",
879
+ borderRadius: 6,
880
+ fontSize: 24,
881
+ fontFamily: "serif"
882
+ },
883
+ children: editor.previewText
884
+ }
885
+ )
886
+ ]
887
+ }
888
+ )
889
+ ]
890
+ }
891
+ );
330
892
  };
331
-
332
893
  exports.NiceFontEditor = NiceFontEditor;
333
894
  exports.exportToSvgFont = exportToSvgFont;
334
895
  exports.useFontEditor = useFontEditor;