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