@oidoid/void 0.1.0-3 → 0.1.0-6

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 (295) hide show
  1. package/dist/meta.json +1 -0
  2. package/dist/package.json +66 -0
  3. package/dist/public/favicon/favicon16.png +0 -0
  4. package/dist/public/favicon/favicon192.png +0 -0
  5. package/dist/public/favicon/favicon32.png +0 -0
  6. package/dist/public/favicon/favicon48.png +0 -0
  7. package/dist/public/favicon/favicon512.png +0 -0
  8. package/dist/public/favicon/favicon64.png +0 -0
  9. package/dist/public/index.html +23 -0
  10. package/dist/public/index.js +3844 -0
  11. package/dist/public/index.js.map +7 -0
  12. package/dist/public/manifest.json +1 -0
  13. package/dist/public/preload-atlas.png +0 -0
  14. package/dist/public/void-v0.1.0-4.html +111 -0
  15. package/dist/public/void-v0.1.0-5.html +111 -0
  16. package/dist/public/void-v0.1.0-6.html +111 -0
  17. package/dist/schema/config-file.d.ts +32 -0
  18. package/dist/schema/config-file.js +41 -0
  19. package/dist/schema/config-file.js.map +1 -0
  20. package/dist/schema/config-file.v0.json +68 -0
  21. package/dist/src/audio.d.ts +7 -0
  22. package/dist/src/audio.js +25 -0
  23. package/dist/src/audio.js.map +1 -0
  24. package/dist/src/demo/assets/manifest.json +46 -0
  25. package/dist/src/demo/assets/preload-atlas.json +193 -0
  26. package/dist/src/demo/ents/clock-ent.d.ts +10 -0
  27. package/dist/src/demo/ents/clock-ent.js +26 -0
  28. package/dist/src/demo/ents/clock-ent.js.map +1 -0
  29. package/dist/src/demo/ents/render-toggle-ent.d.ts +10 -0
  30. package/dist/src/demo/ents/render-toggle-ent.js +38 -0
  31. package/dist/src/demo/ents/render-toggle-ent.js.map +1 -0
  32. package/dist/src/demo/ents/work-counter-ent.d.ts +10 -0
  33. package/dist/src/demo/ents/work-counter-ent.js +24 -0
  34. package/dist/src/demo/ents/work-counter-ent.js.map +1 -0
  35. package/dist/src/demo/game.d.ts +13 -0
  36. package/dist/src/demo/game.js +155 -0
  37. package/dist/src/demo/game.js.map +1 -0
  38. package/dist/src/demo/index.d.ts +1 -0
  39. package/dist/src/demo/index.js +8 -0
  40. package/dist/src/demo/index.js.map +1 -0
  41. package/dist/src/demo/tsconfig.json +15 -0
  42. package/dist/src/demo/types/tag.d.ts +2 -0
  43. package/dist/src/demo/types/tag.js +2 -0
  44. package/dist/src/demo/types/tag.js.map +1 -0
  45. package/dist/src/demo/void.json +11 -0
  46. package/dist/src/ents/button-ent.d.ts +38 -0
  47. package/dist/src/ents/button-ent.js +124 -0
  48. package/dist/src/ents/button-ent.js.map +1 -0
  49. package/dist/src/ents/cursor-ent.d.ts +18 -0
  50. package/dist/src/ents/cursor-ent.js +58 -0
  51. package/dist/src/ents/cursor-ent.js.map +1 -0
  52. package/dist/src/ents/ent.d.ts +7 -0
  53. package/dist/src/ents/ent.js +2 -0
  54. package/dist/src/ents/ent.js.map +1 -0
  55. package/dist/src/ents/follow-cam-ent.d.ts +25 -0
  56. package/dist/src/ents/follow-cam-ent.js +87 -0
  57. package/dist/src/ents/follow-cam-ent.js.map +1 -0
  58. package/dist/src/ents/nine-patch-ent.d.ts +43 -0
  59. package/dist/src/ents/nine-patch-ent.js +143 -0
  60. package/dist/src/ents/nine-patch-ent.js.map +1 -0
  61. package/dist/src/ents/text-ent.d.ts +23 -0
  62. package/dist/src/ents/text-ent.js +109 -0
  63. package/dist/src/ents/text-ent.js.map +1 -0
  64. package/dist/src/ents/zoo.d.ts +16 -0
  65. package/dist/src/ents/zoo.js +38 -0
  66. package/dist/src/ents/zoo.js.map +1 -0
  67. package/dist/src/graphics/atlas-parser.d.ts +4 -0
  68. package/dist/src/graphics/atlas-parser.js +27 -0
  69. package/dist/src/graphics/atlas-parser.js.map +1 -0
  70. package/dist/src/graphics/atlas.d.ts +49 -0
  71. package/dist/src/graphics/atlas.js +7 -0
  72. package/dist/src/graphics/atlas.js.map +1 -0
  73. package/dist/src/graphics/cam.d.ts +89 -0
  74. package/dist/src/graphics/cam.js +211 -0
  75. package/dist/src/graphics/cam.js.map +1 -0
  76. package/dist/src/graphics/gl.d.ts +24 -0
  77. package/dist/src/graphics/gl.js +70 -0
  78. package/dist/src/graphics/gl.js.map +1 -0
  79. package/dist/src/graphics/layer.d.ts +21 -0
  80. package/dist/src/graphics/layer.js +23 -0
  81. package/dist/src/graphics/layer.js.map +1 -0
  82. package/dist/src/graphics/renderer.d.ts +22 -0
  83. package/dist/src/graphics/renderer.js +168 -0
  84. package/dist/src/graphics/renderer.js.map +1 -0
  85. package/dist/src/graphics/sprite-frag.glsl.d.ts +1 -0
  86. package/dist/src/graphics/sprite-frag.glsl.js +35 -0
  87. package/dist/src/graphics/sprite-frag.glsl.js.map +1 -0
  88. package/dist/src/graphics/sprite-vert.glsl.d.ts +1 -0
  89. package/dist/src/graphics/sprite-vert.glsl.js +68 -0
  90. package/dist/src/graphics/sprite-vert.glsl.js.map +1 -0
  91. package/dist/src/graphics/sprite.d.ts +108 -0
  92. package/dist/src/graphics/sprite.js +301 -0
  93. package/dist/src/graphics/sprite.js.map +1 -0
  94. package/dist/src/index.d.ts +30 -0
  95. package/dist/src/index.js +33 -0
  96. package/dist/src/index.js.map +1 -0
  97. package/dist/src/input/context-menu.d.ts +8 -0
  98. package/dist/src/input/context-menu.js +25 -0
  99. package/dist/src/input/context-menu.js.map +1 -0
  100. package/dist/src/input/gamepad.d.ts +18 -0
  101. package/dist/src/input/gamepad.js +49 -0
  102. package/dist/src/input/gamepad.js.map +1 -0
  103. package/dist/src/input/input.d.ts +148 -0
  104. package/dist/src/input/input.js +383 -0
  105. package/dist/src/input/input.js.map +1 -0
  106. package/dist/src/input/keyboard.d.ts +17 -0
  107. package/dist/src/input/keyboard.js +46 -0
  108. package/dist/src/input/keyboard.js.map +1 -0
  109. package/dist/src/input/pointer.d.ts +53 -0
  110. package/dist/src/input/pointer.js +162 -0
  111. package/dist/src/input/pointer.js.map +1 -0
  112. package/dist/src/input/wheel.d.ts +12 -0
  113. package/dist/src/input/wheel.js +30 -0
  114. package/dist/src/input/wheel.js.map +1 -0
  115. package/dist/src/looper.d.ts +15 -0
  116. package/dist/src/looper.js +48 -0
  117. package/dist/src/looper.js.map +1 -0
  118. package/dist/src/mem/pool.d.ts +31 -0
  119. package/dist/src/mem/pool.js +98 -0
  120. package/dist/src/mem/pool.js.map +1 -0
  121. package/dist/src/pixel-ratio-observer.d.ts +10 -0
  122. package/dist/src/pixel-ratio-observer.js +26 -0
  123. package/dist/src/pixel-ratio-observer.js.map +1 -0
  124. package/dist/src/random/random.d.ts +8 -0
  125. package/dist/src/random/random.js +23 -0
  126. package/dist/src/random/random.js.map +1 -0
  127. package/dist/src/storage/local-storage.d.ts +3 -0
  128. package/dist/src/storage/local-storage.js +11 -0
  129. package/dist/src/storage/local-storage.js.map +1 -0
  130. package/dist/src/text/font.d.ts +6 -0
  131. package/dist/src/text/font.js +20 -0
  132. package/dist/src/text/font.js.map +1 -0
  133. package/dist/src/text/mem-prop-5x6.json +573 -0
  134. package/dist/src/text/text-layout.d.ts +19 -0
  135. package/dist/src/text/text-layout.js +85 -0
  136. package/dist/src/text/text-layout.js.map +1 -0
  137. package/dist/src/tsconfig.json +15 -0
  138. package/dist/src/types/geo.d.ts +38 -0
  139. package/dist/src/types/geo.js +61 -0
  140. package/dist/src/types/geo.js.map +1 -0
  141. package/dist/src/types/json.d.ts +31 -0
  142. package/dist/src/types/json.js +2 -0
  143. package/dist/src/types/json.js.map +1 -0
  144. package/dist/src/types/time.d.ts +19 -0
  145. package/dist/src/types/time.js +10 -0
  146. package/dist/src/types/time.js.map +1 -0
  147. package/dist/src/types/void-version.d.ts +9 -0
  148. package/dist/src/types/void-version.js +2 -0
  149. package/dist/src/types/void-version.js.map +1 -0
  150. package/dist/src/utils/async-util.d.ts +11 -0
  151. package/dist/src/utils/async-util.js +36 -0
  152. package/dist/src/utils/async-util.js.map +1 -0
  153. package/dist/src/utils/canvas-util.d.ts +7 -0
  154. package/dist/src/utils/canvas-util.js +128 -0
  155. package/dist/src/utils/canvas-util.js.map +1 -0
  156. package/dist/src/utils/color-util.d.ts +1 -0
  157. package/dist/src/utils/color-util.js +4 -0
  158. package/dist/src/utils/color-util.js.map +1 -0
  159. package/dist/src/utils/debug.d.ts +17 -0
  160. package/dist/src/utils/debug.js +34 -0
  161. package/dist/src/utils/debug.js.map +1 -0
  162. package/dist/src/utils/dom-util.d.ts +3 -0
  163. package/dist/src/utils/dom-util.js +29 -0
  164. package/dist/src/utils/dom-util.js.map +1 -0
  165. package/dist/src/utils/fetch-util.d.ts +3 -0
  166. package/dist/src/utils/fetch-util.js +24 -0
  167. package/dist/src/utils/fetch-util.js.map +1 -0
  168. package/dist/src/utils/math.d.ts +6 -0
  169. package/dist/src/utils/math.js +18 -0
  170. package/dist/src/utils/math.js.map +1 -0
  171. package/dist/src/utils/vibrate.d.ts +1 -0
  172. package/dist/src/utils/vibrate.js +4 -0
  173. package/dist/src/utils/vibrate.js.map +1 -0
  174. package/dist/src/void.d.ts +43 -0
  175. package/dist/src/void.js +95 -0
  176. package/dist/src/void.js.map +1 -0
  177. package/dist/tools/atlas-pack/aseprite.d.ts +58 -0
  178. package/dist/tools/atlas-pack/aseprite.js +16 -0
  179. package/dist/tools/atlas-pack/aseprite.js.map +1 -0
  180. package/dist/tools/atlas-pack/atlas-json-parser.d.ts +21 -0
  181. package/dist/tools/atlas-pack/atlas-json-parser.js +116 -0
  182. package/dist/tools/atlas-pack/atlas-json-parser.js.map +1 -0
  183. package/dist/tools/atlas-pack/atlas-pack.d.ts +2 -0
  184. package/dist/tools/atlas-pack/atlas-pack.js +17 -0
  185. package/dist/tools/atlas-pack/atlas-pack.js.map +1 -0
  186. package/dist/tools/bundle/bundle.d.ts +3 -0
  187. package/dist/tools/bundle/bundle.js +48 -0
  188. package/dist/tools/bundle/bundle.js.map +1 -0
  189. package/dist/tools/bundle/html-plugin.d.ts +3 -0
  190. package/dist/tools/bundle/html-plugin.js +88 -0
  191. package/dist/tools/bundle/html-plugin.js.map +1 -0
  192. package/dist/tools/tsconfig-base.json +45 -0
  193. package/dist/tools/tsconfig.json +21 -0
  194. package/dist/tools/types/config.d.ts +20 -0
  195. package/dist/tools/types/config.js +22 -0
  196. package/dist/tools/types/config.js.map +1 -0
  197. package/dist/tools/utils/argv.d.ts +12 -0
  198. package/dist/tools/utils/argv.js +19 -0
  199. package/dist/tools/utils/argv.js.map +1 -0
  200. package/dist/tools/utils/exec.d.ts +3 -0
  201. package/dist/tools/utils/exec.js +15 -0
  202. package/dist/tools/utils/exec.js.map +1 -0
  203. package/dist/tools/utils/file-util.d.ts +1 -0
  204. package/dist/tools/utils/file-util.js +13 -0
  205. package/dist/tools/utils/file-util.js.map +1 -0
  206. package/dist/tools/utils/html-parser.d.ts +1 -0
  207. package/dist/tools/utils/html-parser.js +10 -0
  208. package/dist/tools/utils/html-parser.js.map +1 -0
  209. package/dist/tools/void.d.ts +17 -0
  210. package/dist/tools/void.js +30 -0
  211. package/dist/tools/void.js.map +1 -0
  212. package/package.json +33 -39
  213. package/readme.md +1 -1
  214. package/schema/config-file.test.ts +55 -0
  215. package/schema/config-file.ts +69 -0
  216. package/schema/config-file.v0.json +68 -0
  217. package/tools/atlas-pack/aseprite.ts +60 -0
  218. package/tools/atlas-pack/atlas-json-parser.test.ts +780 -0
  219. package/tools/atlas-pack/atlas-json-parser.ts +159 -0
  220. package/tools/atlas-pack/atlas-pack.ts +38 -0
  221. package/tools/bundle/bundle.ts +65 -0
  222. package/tools/bundle/html-plugin.ts +135 -0
  223. package/tools/types/config.ts +43 -0
  224. package/tools/utils/argv.test.ts +41 -0
  225. package/tools/utils/argv.ts +29 -0
  226. package/tools/utils/exec.ts +22 -0
  227. package/tools/utils/file-util.ts +11 -0
  228. package/tools/utils/html-parser.ts +9 -0
  229. package/tools/void.ts +54 -0
  230. package/dist/atlas/aseprite.d.ts +0 -37
  231. package/dist/atlas/aseprite.js +0 -2
  232. package/dist/atlas/aseprite.js.map +0 -1
  233. package/dist/atlas/atlas-parser.d.ts +0 -52
  234. package/dist/atlas/atlas-parser.js +0 -109
  235. package/dist/atlas/atlas-parser.js.map +0 -1
  236. package/dist/atlas/atlas.d.ts +0 -12
  237. package/dist/atlas/atlas.js +0 -2
  238. package/dist/atlas/atlas.js.map +0 -1
  239. package/dist/audio/synth.d.ts +0 -4
  240. package/dist/audio/synth.js +0 -21
  241. package/dist/audio/synth.js.map +0 -1
  242. package/dist/graphics/bitmap.d.ts +0 -14
  243. package/dist/graphics/bitmap.js +0 -14
  244. package/dist/graphics/bitmap.js.map +0 -1
  245. package/dist/graphics/cam.d.ts +0 -16
  246. package/dist/graphics/cam.js +0 -42
  247. package/dist/graphics/cam.js.map +0 -1
  248. package/dist/graphics/frag.glsl.d.ts +0 -1
  249. package/dist/graphics/frag.glsl.js +0 -15
  250. package/dist/graphics/frag.glsl.js.map +0 -1
  251. package/dist/graphics/frame-listener.d.ts +0 -16
  252. package/dist/graphics/frame-listener.js +0 -83
  253. package/dist/graphics/frame-listener.js.map +0 -1
  254. package/dist/graphics/renderer.d.ts +0 -12
  255. package/dist/graphics/renderer.js +0 -185
  256. package/dist/graphics/renderer.js.map +0 -1
  257. package/dist/graphics/vert.glsl.d.ts +0 -1
  258. package/dist/graphics/vert.glsl.js +0 -46
  259. package/dist/graphics/vert.glsl.js.map +0 -1
  260. package/dist/index.d.ts +0 -31
  261. package/dist/index.js +0 -79
  262. package/dist/index.js.map +0 -1
  263. package/dist/input/gamepad-poller.d.ts +0 -8
  264. package/dist/input/gamepad-poller.js +0 -38
  265. package/dist/input/gamepad-poller.js.map +0 -1
  266. package/dist/input/input.d.ts +0 -44
  267. package/dist/input/input.js +0 -175
  268. package/dist/input/input.js.map +0 -1
  269. package/dist/input/keyboard-poller.d.ts +0 -7
  270. package/dist/input/keyboard-poller.js +0 -30
  271. package/dist/input/keyboard-poller.js.map +0 -1
  272. package/dist/input/pointer-poller.d.ts +0 -12
  273. package/dist/input/pointer-poller.js +0 -67
  274. package/dist/input/pointer-poller.js.map +0 -1
  275. package/dist/sprite/sprite.d.ts +0 -51
  276. package/dist/sprite/sprite.js +0 -161
  277. package/dist/sprite/sprite.js.map +0 -1
  278. package/dist/storage/json-storage.d.ts +0 -4
  279. package/dist/storage/json-storage.js +0 -13
  280. package/dist/storage/json-storage.js.map +0 -1
  281. package/dist/test/tsconfig.json +0 -14
  282. package/dist/text/font.d.ts +0 -6
  283. package/dist/text/font.js +0 -18
  284. package/dist/text/font.js.map +0 -1
  285. package/dist/text/text-layout.d.ts +0 -11
  286. package/dist/text/text-layout.js +0 -73
  287. package/dist/text/text-layout.js.map +0 -1
  288. package/dist/tsconfig.json +0 -13
  289. package/dist/types/2d.d.ts +0 -9
  290. package/dist/types/2d.js +0 -2
  291. package/dist/types/2d.js.map +0 -1
  292. package/dist/void.js +0 -60
  293. package/dist/void.js.map +0 -7
  294. package/dist/void.meta.json +0 -299
  295. package/tools/void.js +0 -143
@@ -0,0 +1,159 @@
1
+ import {
2
+ type Anim,
3
+ type AtlasJSON,
4
+ animCels,
5
+ animMillis,
6
+ celMillis,
7
+ type TagFormat
8
+ } from '../../src/graphics/atlas.ts'
9
+ import {type Box, boxEq, type XY} from '../../src/types/geo.ts'
10
+ import {
11
+ type Aseprite,
12
+ AsepriteDirection,
13
+ type AsepriteFrame,
14
+ type AsepriteFrameMap,
15
+ type AsepriteFrameTag,
16
+ type AsepriteSlice,
17
+ type AsepriteTagSpan
18
+ } from './aseprite.ts'
19
+
20
+ export function parseAtlasJSON(ase: Readonly<Aseprite>): AtlasJSON {
21
+ const anim: {[tag: string]: Anim} = {}
22
+ const cels: number[] = []
23
+ for (const span of ase.meta.frameTags) {
24
+ const tag = parseTag(span.name)
25
+ if (anim[tag]) throw Error(`atlas tag "${tag}" duplicate`)
26
+ const id = Object.keys(anim).length
27
+ anim[tag] = parseAnim(id, span, ase.frames, ase.meta.slices)
28
+ for (const cel of parseAnimFrames(span, ase.frames).map(parseCel))
29
+ cels.push(cel.x, cel.y)
30
+ }
31
+ for (const slice of ase.meta.slices)
32
+ if (!anim[parseTag(slice.name)])
33
+ throw Error(`atlas hitbox "${slice.name}" has no animation`)
34
+ return {anim, celXY: cels}
35
+ }
36
+
37
+ /** @internal */
38
+ export function parseAnim(
39
+ id: number,
40
+ span: Readonly<AsepriteTagSpan>,
41
+ map: Readonly<AsepriteFrameMap>,
42
+ slices: readonly Readonly<AsepriteSlice>[]
43
+ ): Anim {
44
+ const cels = parseAnimFrames(span, map)
45
+ if (!cels[0]) throw Error(`no atlas frame "${span.name}"`)
46
+ const {hitbox, hurtbox} = parseHitboxes(span, slices)
47
+ return {
48
+ cels: cels.length,
49
+ h: cels[0].sourceSize.h,
50
+ hitbox,
51
+ hurtbox,
52
+ id,
53
+ w: cels[0].sourceSize.w
54
+ }
55
+ }
56
+
57
+ /**
58
+ * every frame is guaranteed to be present for at least `celMillis`. cels are
59
+ * duplicated until cel duration is at least met. cels are unpacked until a full
60
+ * period is defined for the direction. no warns for overflowing past on second
61
+ * or uneven periods.
62
+ * @internal
63
+ */
64
+ export function parseAnimFrames(
65
+ span: AsepriteTagSpan,
66
+ map: AsepriteFrameMap
67
+ ): AsepriteFrame[] {
68
+ const cels = []
69
+ let animDuration = 0
70
+ const len = span.to - span.from + 1
71
+ const peak = len - 1
72
+ const cycle = Math.max(1, 2 * peak)
73
+ const end =
74
+ span.direction === AsepriteDirection.Forward ||
75
+ span.direction === AsepriteDirection.Reverse
76
+ ? len
77
+ : cycle
78
+ const indexByDir: {[dir in AsepriteDirection]: (i: number) => number} = {
79
+ forward: i => span.from + (i % len),
80
+ pingpong: i => span.from + peak - Math.abs((i % cycle) - peak),
81
+ pingpong_reverse: i => span.to - (peak - Math.abs((i % cycle) - peak)),
82
+ reverse: i => span.to - (i % len)
83
+ }
84
+ const frameIndex = indexByDir[span.direction as AsepriteDirection]
85
+ for (
86
+ let i = 0;
87
+ i < end && cels.length < animCels && animDuration < animMillis;
88
+ i++
89
+ ) {
90
+ const frameTag = `${span.name}--${frameIndex(i)}` as AsepriteFrameTag
91
+ const frame = map[frameTag]
92
+ if (!frame) throw Error(`no atlas frame "${frameTag}"`)
93
+ for (
94
+ let celDuration = 0;
95
+ celDuration < frame.duration &&
96
+ cels.length < animCels &&
97
+ animDuration < animMillis;
98
+ celDuration += celMillis, animDuration += celMillis
99
+ ) {
100
+ cels.push(frame)
101
+
102
+ if (span.from === span.to) return cels // optimize for long single cel.
103
+ }
104
+ }
105
+ return cels
106
+ }
107
+
108
+ /** @internal */
109
+ export function parseCel(frame: Readonly<AsepriteFrame>): XY {
110
+ return {
111
+ x: frame.frame.x + (frame.frame.w - frame.sourceSize.w) / 2,
112
+ y: frame.frame.y + (frame.frame.h - frame.sourceSize.h) / 2
113
+ }
114
+ }
115
+
116
+ /** @internal */
117
+ export function parseHitboxes(
118
+ span: Readonly<AsepriteTagSpan>,
119
+ slices: readonly Readonly<AsepriteSlice>[]
120
+ ): {hitbox: Box | undefined; hurtbox: Box | undefined} {
121
+ let hitbox
122
+ let hurtbox
123
+ // https://github.com/aseprite/aseprite/issues/3524
124
+ for (const slice of slices) {
125
+ if (slice.name !== span.name) continue
126
+
127
+ if (!slice.keys[0]) continue
128
+
129
+ for (const k of slice.keys)
130
+ if (!boxEq(k.bounds, slice.keys[0].bounds))
131
+ throw Error(
132
+ `atlas tag "${span.name}" hitbox bounds varies across frames`
133
+ )
134
+
135
+ const red = slice.color === '#ff0000ff'
136
+ const green = slice.color === '#00ff00ff'
137
+ const blue = slice.color === '#0000ffff' // default Aseprite slice color.
138
+ if (!red && !green && !blue)
139
+ throw Error(
140
+ `atlas tag "${span.name}" hitbox color ${slice.color} unsupported`
141
+ )
142
+
143
+ if (hitbox && (red || blue))
144
+ throw Error(`atlas tag "${span.name}" has multiple hitboxes`)
145
+
146
+ if (hurtbox && (green || blue))
147
+ throw Error(`atlas tag "${span.name}" has multiple hurtboxes`)
148
+
149
+ if (red || blue) hitbox = slice.keys[0].bounds
150
+ if (green || blue) hurtbox = slice.keys[0].bounds
151
+ }
152
+ return {hitbox, hurtbox}
153
+ }
154
+
155
+ function parseTag(tag: string): TagFormat {
156
+ if (!tag.includes('--'))
157
+ throw Error(`atlas tag "${tag}" not in <filestem>--<animation> format`)
158
+ return tag as TagFormat
159
+ }
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import type {AtlasConfig} from '../../schema/config-file.ts'
4
+ import {exec} from '../utils/exec.ts'
5
+ import {globAll} from '../utils/file-util.ts'
6
+ import {parseAtlasJSON} from './atlas-json-parser.ts'
7
+
8
+ // to-do: separate executable?
9
+ export async function packAtlas(
10
+ config: Readonly<AtlasConfig>,
11
+ minify: boolean
12
+ ): Promise<void> {
13
+ const filenames = await globAll(path.join(config.dir, '**.aseprite'))
14
+ if (!filenames.length) return
15
+
16
+ const json = await exec(
17
+ 'aseprite',
18
+ '--batch',
19
+ '--color-mode=indexed',
20
+ '--filename-format={title}--{tag}--{frame}',
21
+ '--list-slices',
22
+ '--list-tags',
23
+ '--merge-duplicates',
24
+ `--sheet=${config.image}`,
25
+ '--sheet-pack',
26
+ '--tagname-format={title}--{tag}',
27
+ ...filenames
28
+ )
29
+
30
+ if (minify) await exec('zopflipng', '-y', config.image, config.image)
31
+
32
+ await fs.writeFile(
33
+ config.json,
34
+ JSON.stringify(parseAtlasJSON(JSON.parse(json)))
35
+ )
36
+
37
+ await exec('biome', 'check', '--fix', config.json)
38
+ }
@@ -0,0 +1,65 @@
1
+ import fs from 'node:fs'
2
+ import esbuild from 'esbuild'
3
+ import type {AtlasConfig} from '../../schema/config-file.ts'
4
+ import * as V from '../../src/index.ts'
5
+ import type {VoidVersion} from '../../src/types/void-version.ts'
6
+ import {packAtlas} from '../atlas-pack/atlas-pack.ts'
7
+ import type {Config} from '../types/config.ts'
8
+ import {HTMLPlugin} from './html-plugin.ts'
9
+
10
+ export async function bundle(
11
+ config: Readonly<Config>,
12
+ srcFilenames: readonly string[],
13
+ voidVersion: Readonly<VoidVersion>
14
+ ): Promise<void> {
15
+ const opts: esbuild.BuildOptions = {
16
+ banner: config.watch
17
+ ? {
18
+ js: "new EventSource('/esbuild').addEventListener('change', () => location.reload());"
19
+ }
20
+ : {},
21
+ bundle: true,
22
+ define: {
23
+ // define on globalThis to avoid ReferenceError in unit tests.
24
+ 'globalThis.voidVersion': JSON.stringify(voidVersion)
25
+ },
26
+ entryPoints: [...srcFilenames],
27
+ format: 'esm',
28
+ logLevel: 'info', // print the port and build demarcations.
29
+ metafile: true,
30
+ minify: config.minify,
31
+ outdir: config.out.dir,
32
+ plugins: [HTMLPlugin(config)],
33
+ sourcemap: 'linked',
34
+ target: 'es2024' // https://esbuild.github.io/content-types/#tsconfig-json
35
+ }
36
+
37
+ if (config.preloadAtlas) await packAtlas(config.preloadAtlas, config.minify)
38
+ if (config.preloadAtlas && config.watch) {
39
+ fs.watch(config.preloadAtlas.dir, {recursive: true}, (ev, type) =>
40
+ onWatch(config.preloadAtlas!, config.minify, ev, type)
41
+ )
42
+ const ctx = await esbuild.context(opts)
43
+ await Promise.all([
44
+ ctx.watch(),
45
+ ctx.serve({port: 1234, servedir: config.out.dir})
46
+ ])
47
+ } else {
48
+ const build = await esbuild.build(opts)
49
+ if (config.meta)
50
+ await fs.promises.writeFile(config.meta, JSON.stringify(build.metafile))
51
+ }
52
+ }
53
+
54
+ const onWatch = V.debounce(
55
+ async (
56
+ config: Readonly<AtlasConfig>,
57
+ minify: boolean,
58
+ ev: fs.WatchEventType,
59
+ file: string | null
60
+ ) => {
61
+ console.log(`asset ${file} ${ev}.`)
62
+ await packAtlas(config, minify)
63
+ },
64
+ 500 as V.Millis
65
+ )
@@ -0,0 +1,135 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import type esbuild from 'esbuild'
4
+ import {minify} from 'html-minifier-next'
5
+ import type {Config} from '../types/config.ts'
6
+ import {exec} from '../utils/exec.ts'
7
+ import {parseHTML} from '../utils/html-parser.ts'
8
+
9
+ type Manifest = {
10
+ icons?:
11
+ | {
12
+ src?: string | undefined
13
+ size?: string | undefined
14
+ type?: string | undefined
15
+ }[]
16
+ | undefined
17
+ start_url?: string | undefined
18
+ version?: string | undefined
19
+ }
20
+
21
+ export function HTMLPlugin(config: Readonly<Config>): esbuild.Plugin {
22
+ return {
23
+ name: 'HTMLPlugin',
24
+ setup(build) {
25
+ build.onEnd(async _result => {
26
+ const doc = await parseHTML(config.entry)
27
+
28
+ const scripts = doc.querySelectorAll<HTMLScriptElement>(
29
+ "script[type='module'][src$='.ts']"
30
+ )
31
+ for (const script of scripts) {
32
+ // to-do: test.
33
+ const filename = path.basename(
34
+ script.getAttribute('src')!.replace(/\.ts$/, '.js')
35
+ )
36
+ if (config.oneFile) {
37
+ script.removeAttribute('src')
38
+ script.textContent = await fs.readFile(
39
+ path.join(config.out.dir, filename),
40
+ {encoding: 'utf8'}
41
+ )
42
+ } else script.src = filename
43
+ }
44
+
45
+ if (config.oneFile) {
46
+ const imgs = doc.querySelectorAll<HTMLImageElement>('img[src]')
47
+ for (const img of imgs) {
48
+ const filename = img.getAttribute('src')!
49
+ const b64 = await fs.readFile(path.join(config.out.dir, filename), {
50
+ encoding: 'base64'
51
+ })
52
+ const ext = filename.match(/\.([^.]+)$/)?.[1]
53
+ if (!ext) throw Error('no asset extension')
54
+ img.src = `data:image/${ext};base64,${b64}`
55
+ }
56
+ }
57
+
58
+ const manifestEl = doc.querySelector<HTMLLinkElement>(
59
+ 'link[href][rel="manifest"]'
60
+ )
61
+ if (manifestEl) {
62
+ const manifestFilename = path.resolve(
63
+ path.dirname(config.entry),
64
+ manifestEl.getAttribute('href')!
65
+ )
66
+ const manifest: Manifest = JSON.parse(
67
+ await fs.readFile(manifestFilename, 'utf8')
68
+ )
69
+ if (process.env.npm_package_version)
70
+ manifest.version = process.env.npm_package_version
71
+
72
+ for (const icon of manifest.icons ?? []) {
73
+ if (!icon.src || !icon.type) continue
74
+ const iconFilename = `${path.dirname(manifestFilename)}/${icon.src}`
75
+ const file = await fs.readFile(iconFilename)
76
+ if (config.oneFile)
77
+ icon.src = `data:${icon.type};base64,${file.toString('base64')}`
78
+ else icon.src = path.relative(config.out.dir, iconFilename)
79
+ }
80
+ if (config.watch) manifest.start_url = 'http://localhost:1234'
81
+
82
+ if (config.oneFile)
83
+ manifestEl.href = `data:application/json,${encodeURIComponent(
84
+ JSON.stringify(manifest)
85
+ )}`
86
+ else
87
+ await fs.writeFile(
88
+ path.join(config.out.dir, path.basename(manifestFilename)),
89
+ JSON.stringify(manifest)
90
+ )
91
+ }
92
+
93
+ if (config.oneFile) {
94
+ const icons = doc.querySelectorAll<HTMLLinkElement>(
95
+ 'link[href][rel="icon"][type]'
96
+ )
97
+ for (const icon of icons) {
98
+ const filename = path.resolve(
99
+ config.out.dir,
100
+ icon.getAttribute('href')!
101
+ )
102
+ const file = await fs.readFile(filename)
103
+ icon.href = `data:${icon.type};base64,${file.toString('base64')}`
104
+ }
105
+ }
106
+
107
+ let html = `<!doctype html>\n${doc.documentElement.outerHTML}`
108
+
109
+ if (config.minify)
110
+ html = await minify(html, {
111
+ caseSensitive: true,
112
+ collapseBooleanAttributes: true,
113
+ html5: true,
114
+ minifyCSS: true,
115
+ removeAttributeQuotes: true,
116
+ removeComments: true,
117
+ removeEmptyAttributes: true
118
+ })
119
+ // to-do: what biome config does this use outside of the repo?
120
+ else
121
+ html = await exec(
122
+ 'biome',
123
+ 'check',
124
+ '--fix',
125
+ `--stdin-file-path=${config.entry}`,
126
+ {stdin: html}
127
+ )
128
+
129
+ // to-do: test.
130
+ const outHTMLFilename = path.join(config.out.dir, config.out.filename)
131
+ await fs.writeFile(outHTMLFilename, html)
132
+ })
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,43 @@
1
+ import path from 'node:path'
2
+ import type {AtlasConfig, ConfigFile} from '../../schema/config-file.ts'
3
+ import type {Argv} from '../utils/argv.ts'
4
+
5
+ export type Config = {
6
+ $schema: string
7
+ entry: string
8
+ meta: string | undefined
9
+ out: {dir: string; filename: string}
10
+ preloadAtlas: AtlasConfig | undefined
11
+
12
+ /** config directory name. */
13
+ dirname: string
14
+ /** config filename. */
15
+ filename: string
16
+
17
+ minify: boolean
18
+ oneFile: boolean
19
+ watch: boolean
20
+ }
21
+
22
+ export function Config(
23
+ configFile: Readonly<ConfigFile>,
24
+ argv: Readonly<Argv>,
25
+ version: string | undefined
26
+ ): Config {
27
+ let stem = path.basename(configFile.entry).replace(/\.[^.]+$/, '')
28
+ if (configFile.out.name && !argv.opts['--watch']) stem = configFile.out.name
29
+ let suffix = '.html'
30
+ if (version && !argv.opts['--watch']) suffix = `-v${version}.html`
31
+ return {
32
+ $schema: configFile.$schema,
33
+ entry: configFile.entry,
34
+ meta: configFile.meta,
35
+ out: {dir: configFile.out.dir, filename: `${stem}${suffix}`},
36
+ preloadAtlas: configFile.preloadAtlas,
37
+ dirname: configFile.dirname,
38
+ filename: configFile.filename,
39
+ minify: argv.opts['--minify'] ?? false,
40
+ oneFile: argv.opts['--one-file'] ?? false,
41
+ watch: argv.opts['--watch'] ?? false
42
+ }
43
+ }
@@ -0,0 +1,41 @@
1
+ import assert from 'node:assert/strict'
2
+ import {test} from 'node:test'
3
+ import {Argv} from './argv.ts'
4
+
5
+ declare module './argv.ts' {
6
+ interface Opts {
7
+ '--config'?: string
8
+ '--minify'?: true
9
+ '--watch'?: true
10
+ }
11
+ }
12
+
13
+ test('parses empty.', () => {
14
+ assert.deepEqual<Argv>(Argv(['/usr/local/bin/node', 'tools/void.ts']), {
15
+ args: [],
16
+ opts: {},
17
+ posargs: []
18
+ })
19
+ })
20
+
21
+ test('parses nonempty.', () => {
22
+ assert.deepEqual<Argv>(
23
+ Argv([
24
+ '/usr/local/bin/node',
25
+ 'tools/void.ts',
26
+ '--config=config',
27
+ '--minify',
28
+ '--watch',
29
+ 'a.aseprite',
30
+ 'b.aseprite',
31
+ '--',
32
+ 'posarg0',
33
+ 'posarg1'
34
+ ]),
35
+ {
36
+ args: ['a.aseprite', 'b.aseprite'],
37
+ opts: {'--config': 'config', '--minify': true, '--watch': true},
38
+ posargs: ['posarg0', 'posarg1']
39
+ }
40
+ )
41
+ })
@@ -0,0 +1,29 @@
1
+ export type Argv = {
2
+ /** all the arguments not starting with `--` before `--`. */
3
+ args: string[]
4
+ /** all options starting with `--` before `--` and their optional value. */
5
+ opts: Opts
6
+ /** everything after `--`. */
7
+ posargs: string[]
8
+ }
9
+
10
+ /** string-string|bool map. Eg, `'--config'?: string; '--minify'?: string | true` */
11
+ // biome-ignore lint/suspicious/noEmptyInterface: declaration merging.
12
+ export interface Opts {}
13
+
14
+ export function Argv(argv: readonly string[]): Argv {
15
+ const args = []
16
+ const posargs = []
17
+ const opts: {[k: string]: string | true} = {}
18
+ for (let i = 2; i < argv.length; i++) {
19
+ if (argv[i] === '--') {
20
+ posargs.push(...argv.slice(i + 1))
21
+ break
22
+ }
23
+ if (argv[i]!.startsWith('--')) {
24
+ const [k, v] = argv[i]!.split(/=(.*)/).slice(0, 2)
25
+ opts[k!] = v ?? true
26
+ } else args.push(argv[i]!)
27
+ }
28
+ return {args, opts, posargs}
29
+ }
@@ -0,0 +1,22 @@
1
+ import {execFile} from 'node:child_process'
2
+ import util from 'node:util'
3
+
4
+ export async function exec(
5
+ exe: string,
6
+ ...args:
7
+ | readonly [...string[], {stdin?: string | undefined}]
8
+ | readonly string[]
9
+ ): Promise<string> {
10
+ const opts = (typeof args.at(-1) === 'object' ? args.at(-1) : undefined) as
11
+ | {stdin?: string | undefined}
12
+ | undefined
13
+ args = (opts ? args.slice(0, -1) : args) as string[]
14
+ const promise = util.promisify(execFile)(exe, args, {})
15
+ if (promise.child.stdin && opts?.stdin != null) {
16
+ promise.child.stdin.write(opts.stdin)
17
+ promise.child.stdin.end()
18
+ }
19
+ const {stdout, stderr} = await promise
20
+ process.stderr.write(stderr)
21
+ return stdout
22
+ }
@@ -0,0 +1,11 @@
1
+ import fs from 'node:fs/promises'
2
+
3
+ export async function globAll(glob: string): Promise<string[]> {
4
+ const filenames = []
5
+ try {
6
+ for await (const filename of fs.glob(glob)) filenames.push(filename)
7
+ } catch (err) {
8
+ throw Error(`glob \`${glob}\` unreadable`, {cause: err})
9
+ }
10
+ return filenames
11
+ }
@@ -0,0 +1,9 @@
1
+ import {JSDOM} from 'jsdom'
2
+
3
+ export async function parseHTML(filename: string): Promise<Document> {
4
+ try {
5
+ return (await JSDOM.fromFile(filename)).window.document
6
+ } catch (err) {
7
+ throw Error(`HTML ${filename} unparsable`, {cause: err})
8
+ }
9
+ }
package/tools/void.ts ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ // void.ts --config=<void.json> [--minify] [--one-file] [--watch]
3
+ // compiles images into an atlas and bundles an HTML entrypoint.
4
+
5
+ import path from 'node:path'
6
+ import packageJSON from '../package.json' with {type: 'json'}
7
+ import {parseConfigFile} from '../schema/config-file.ts'
8
+ import type {VoidVersion} from '../src/types/void-version.ts'
9
+ import {bundle} from './bundle/bundle.ts'
10
+ import {Config} from './types/config.ts'
11
+ import {Argv} from './utils/argv.ts'
12
+ import {exec} from './utils/exec.ts'
13
+ import {parseHTML} from './utils/html-parser.ts'
14
+
15
+ declare module './utils/argv.ts' {
16
+ interface Opts {
17
+ '--config'?: string
18
+ '--minify'?: true
19
+ /** inline everything into a single HTML file output. */
20
+ '--one-file'?: true
21
+ /**
22
+ * run development server on http://localhost:1234 and reload on code
23
+ * change.
24
+ */
25
+ '--watch'?: true
26
+ }
27
+ }
28
+
29
+ export async function build(
30
+ args: readonly string[],
31
+ env: {readonly [name: string]: string | undefined}
32
+ ): Promise<void> {
33
+ const argv = Argv(args)
34
+ const configFile = await parseConfigFile(argv.opts['--config'] ?? 'void.json')
35
+ const config = Config(configFile, argv, env.npm_package_version)
36
+
37
+ const doc = await parseHTML(config.entry)
38
+ const srcFilenames = [
39
+ ...doc.querySelectorAll<HTMLScriptElement>(
40
+ "script[type='module'][src$='.ts']"
41
+ )
42
+ ].map(el => path.resolve(path.dirname(config.entry), el.getAttribute('src')!))
43
+
44
+ const voidVersion: VoidVersion = {
45
+ hash: (await exec('git', 'rev-parse', '--short', 'HEAD')).trim(),
46
+ published: packageJSON.published,
47
+ // imported JSON doesn't treeshake. define as a constant.
48
+ version: packageJSON.version
49
+ }
50
+
51
+ await bundle(config, srcFilenames, voidVersion)
52
+ }
53
+
54
+ if (import.meta.main) await build(process.argv, process.env)
@@ -1,37 +0,0 @@
1
- import type { Box, WH } from '../types/2d.js';
2
- /** https://github.com/aseprite/aseprite/blob/master/docs/ase-file-specs.md */
3
- export type Aseprite = {
4
- readonly meta: AsepriteMeta;
5
- readonly frames: AsepriteFrameMap;
6
- };
7
- export type AsepriteFrameMap = {
8
- readonly [key: AsepriteAnimTagFrame]: AsepriteFrame;
9
- };
10
- export type AsepriteMeta = {
11
- /** `--list-tags`. */
12
- readonly frameTags: readonly AsepriteTagSpan[];
13
- /** `--list-slices`. */
14
- readonly slices: readonly AsepriteSlice[];
15
- };
16
- /** `--filename-format='{title}--{tag}--{frame}'`. */
17
- export type AsepriteAnimTagFrame = `${AnimTag}--${bigint}`;
18
- /** `--tagname-format={title}--{tag}`. */
19
- export type AnimTag = `${string}--${string}`;
20
- export type AsepriteFrame = {
21
- /** Bounds including padding. */
22
- readonly frame: Readonly<Box>;
23
- /** WH without padding. */
24
- readonly sourceSize: Readonly<WH>;
25
- };
26
- export type AsepriteTagSpan = {
27
- readonly name: AnimTag | string;
28
- readonly from: number;
29
- /** The inclusive ending index, possibly equal to from. */
30
- readonly to: number;
31
- };
32
- export type AsepriteSlice = {
33
- readonly name: AnimTag | string;
34
- readonly keys: readonly {
35
- readonly bounds: Readonly<Box>;
36
- }[];
37
- };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=aseprite.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"aseprite.js","sourceRoot":"","sources":["../../src/atlas/aseprite.ts"],"names":[],"mappings":""}