litecanvas 0.99.0 → 0.100.1

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Litecanvas
6
6
 
7
- [![NPM Version](https://badgen.net/npm/v/litecanvas?cache=300&scale=1.1&label=NPM&color=2f9e44)](https://www.npmjs.com/package/litecanvas/)  
7
+ [![NPM Version](https://badgen.net/npm/v/litecanvas?scale=1.1&label=NPM&color=2f9e44)](https://www.npmjs.com/package/litecanvas/)  
8
8
  [![License](https://badgen.net/npm/license/litecanvas?scale=1.1)](LICENSE)
9
9
 
10
10
  Litecanvas is a lightweight HTML5 canvas 2D engine suitable for small web games, prototypes, game jams, animations, creative coding, learning game programming and game design, etc.
@@ -30,22 +30,24 @@ Litecanvas is a lightweight HTML5 canvas 2D engine suitable for small web games,
30
30
 
31
31
  ## Getting Started
32
32
 
33
- ### Installation
33
+ You can get started using our [online playground](https://litecanvas.github.io) without installing nothing.
34
34
 
35
- You can get started using our [online playground](https://litecanvas.github.io).
35
+ If you want to test locally, just use one of the installation options.
36
36
 
37
- Or installing our package via NPM:
37
+ ### HTML/CDN
38
38
 
39
- ```sh
40
- npm i litecanvas
41
- ```
42
-
43
- Or just create a HTML file and add a `<script>` tag with our CDN link:
39
+ Create a HTML file and add a `<script>` tag with our CDN link:
44
40
 
45
41
  ```html
46
42
  <script src="https://unpkg.com/litecanvas"></script>
47
43
  ```
48
44
 
45
+ ### Template
46
+
47
+ For those who are familiar with Node/NPM, we have a [basic template](https://github.com/litecanvas/template).
48
+
49
+ ## API
50
+
49
51
  ### Basic game structure
50
52
 
51
53
  ```js
@@ -104,7 +106,7 @@ Each time a Litecanvas' function ask for a color, you should use an of theses co
104
106
 
105
107
  ```js
106
108
  // example: draw a white rectangle
107
- color = 3
109
+ let color = 3
108
110
  rectfill(0, 0, 32, 32, color)
109
111
  ```
110
112
 
@@ -117,11 +119,17 @@ function draw() {
117
119
  // clear and fill the game screen with color #0 (black)
118
120
  cls(0)
119
121
 
120
- // print a red "Hello" text at x=0, y=0
121
- text(0, 0, 'Hello', 4)
122
+ // print a red text at x=0, y=0
123
+ text(0, 0, 'Hello!!!', 4)
124
+
125
+ // use \n to break text lines
126
+ text(0, 30, 'multi\nline\ntext')
122
127
  }
123
128
  ```
124
129
 
130
+ [Live Demo](https://litecanvas.js.org?c=eJxVzEsKwkAMBuB9T%2FEXF52CYEG37j1EN3EadTBmZCb1gXh3pyqIIYs8PwnGnvRC2bVVtRvVW4iKIdHVtXhUKLFYwAtTAumAXRCBHRh7OjGyT8yKa7ADfJSYMOvgtkL%2B2L5%2FvWTXFfnrnFNQAyHxAONbKQ23dTfHfd29b6ahK33JZsMisa7rZo7Vjxgzo1dYxDYxHT%2BMBOX8Bywn4TSKhV6nba%2FTpmmr5wtwJEcj)
131
+
132
+
125
133
  ### Drawing shapes
126
134
 
127
135
  You can use the following functions to draw shapes:
@@ -139,18 +147,20 @@ litecanvas()
139
147
  function draw() {
140
148
  cls(0)
141
149
 
142
- // draw a color filled rectangle at x=0 and y=0
150
+ // draw a color filled rectangle at x=10 and y=20
143
151
  // with width=32 and height=32
144
152
  // and color=3 (white)
145
- rectfill(0, 0, 32, 32, 3)
153
+ rectfill(10, 20, 32, 32, 3)
146
154
 
147
- // draw a circle outline at x=64 and y=32
148
- // with radius=40
155
+ // draw a circle outline at x=64 and y=96
156
+ // with radius=50
149
157
  // and color=5 (yellow)
150
- circ(64, 32, 40, 5)
158
+ circ(64, 96, 50, 5)
151
159
  }
152
160
  ```
153
161
 
162
+ [Live Demo](https://litecanvas.js.org?c=eJxljk0KwyAQhfc5xSwNBGJ%2BFLrwMKImCoOCMU1D6d2rabIoGZhBfeN7H7pklPRPuZC6qqbVq%2BSCBx3lRmp4V5BL4UJoVsu5bQ8NJKiAIcLkEI2GaFSSfkYDMsFLdBSk17CLnl6%2FNpdsHjpZMfSHao2bbcq3a6U8Hq5iALLZTFYfUjEvOaSjDfS5h%2F7sO5SLKkOENaHzJwwfT5gH%2F4OJUrt1EYze8xmQ3SCG7QdQXAkfG3jwBlgGYHX1%2BQLFaFeI)
163
+
154
164
  ### Drawing sprites
155
165
 
156
166
  ```js
@@ -162,7 +172,7 @@ litecanvas({
162
172
  // each visible char is a pixel
163
173
  // numbers are colors
164
174
  // dots are transparent pixels
165
- const smile = `
175
+ let smile8x8 = `
166
176
  .555555.
167
177
  55555555
168
178
  55055055
@@ -176,26 +186,26 @@ function draw() {
176
186
  cls(0)
177
187
 
178
188
  spr(
179
- 0, 0, // position X Y
180
- 8, 8, // sprite Width and Height
181
- smile // the pixels
189
+ 0, 0, // position X Y
190
+ 8, 8, // the sprite Width and Height
191
+ smile8x8 // the sprite Pixels
182
192
  )
183
193
  }
184
194
  ```
185
195
 
196
+ [Live Demo](https://litecanvas.js.org?c=eJxtUMtqwzAQvOsr5mhDSNxCIBR67x%2B0vUWR1UigSEa7zoOQf%2B9KdkwPHfawuzOzSBM8W6PjWVNzV8DF9%2Bze8PK6U49Wqc0GtzRCBDDZaragIYuDRMgOxNnHIxWZ1cbh7MkfgoVxOsMTNAZ%2FtaHwcTwdbJZVFjqFlKurTzytOOtIg3SRJw%2BpYBl08nLuHXt5GrDeVqzrsJ0xD12t%2F4a%2Fsq603SITzMzz9F6pnzEa9imiz%2FrStLhXgQnUdJJI6SWDpjYF3aqU%2FGVI5KvvC98Lu1uVEnbKDZ8lX%2BjY48P6o%2BNFOP1UhOzsM4Gyb9XjF0Vyb7o%3D)
197
+
186
198
  ### Creating and drawing images
187
199
 
188
200
  ```js
189
201
  litecanvas()
190
202
 
191
- // lets create the flag of the Japan
192
- const japan = paint(
193
- 48, 32, // image width and height
203
+ // lets create flag of Japan
204
+ let japanFlag = paint(
205
+ 48, 32, // the image width and height
194
206
  function () {
195
- // the result of this drawings
196
- // goes to an offscreen canvas
197
- rectfill(0, 0, 48, 32, 3)
198
- circfill(24, 16, 8, 4)
207
+ cls(3) // white background
208
+ circfill(24, 16, 8, 4) // red circle
199
209
  }, {
200
210
  // you can scale your image
201
211
  // by default, scale=1
@@ -206,25 +216,31 @@ const japan = paint(
206
216
  function draw() {
207
217
  cls(0)
208
218
 
209
- // now the japan variable holds a image
210
- image(W/2 - japan.width/2, H/2 - japan.height/2, japan)
219
+ // draw the japanFlag image
220
+ image(
221
+ W/2 - japanFlag.width/2, // game screen center X
222
+ H/2 - japanFlag.height/2, // game screen center Y
223
+ japanFlag // the image
224
+ )
211
225
  }
212
226
  ```
213
227
 
214
- It's very useful when you need to draw something the same way every time. This way, you create an image of that drawing, working as a kind of cache.
228
+ [Live Demo](https://litecanvas.js.org?c=eJxtUctOwzAQvPsr5phIgdA0QgipV4T4AeC4dTaJi%2BtUjkNUof47toOcqmJPXs%2FMPma1cizJfNOY5UKUJTS7EdIyOUarqcPQ4o1OZIRHcAivl%2FC9w4mUcZmAj%2FqpwLYq4PWuZ6gjdYxZNa4HmQY9q653kdlORjo1GGQ5fuJPCKnHbJsH%2Bdz7gbAn%2BdXZYTLNSlFWtkrrrKoLbB4L%2BJZ1lFhuIqo5ki%2FFVWEPn4cJfkOMkjSHzC7zXXP2ZzTc0qRdsfB2mwTH%2FBn1Ulx4l9IOjaU57RF2ePDoX8mARTNWy9a28ZWlFu9lhbuVeB%2BdK72foVBHR%2FZDWGYDycaxxUdSvt4oF6fL5RT%2FKD%2BTch3rNq6PGOm5uPwCs6aXUg%3D%3D)
215
229
 
216
- You can also use the `image()` function to draw PNG/JPG images, but you'll need to load them first:
230
+ > Note: It's very useful when you need to draw something the same way every time. This way, you create an image of that drawing, working as a kind of cache.
231
+
232
+ You can also draw PNG/JPG image files, but you'll need to load them first:
217
233
 
218
234
  ```js
219
235
  litecanvas()
220
236
 
221
- let PngImage
237
+ let myImage
222
238
 
223
239
  function init() {
224
240
  // load a image from its URL
225
- const img = new Image()
241
+ let img = new Image()
226
242
  img.onload = () => {
227
- PngImage = img
243
+ myImage = img
228
244
  }
229
245
  img.src = 'https://litecanvas.js.org/icons/icon-128.png'
230
246
  }
@@ -232,17 +248,19 @@ function init() {
232
248
  function draw() {
233
249
  cls(0)
234
250
 
235
- if (!PngImage) {
251
+ if (!myImage) {
236
252
  // if not loaded, show this message
237
253
  text(10, 10, 'Loading image...')
238
254
  } else {
239
255
  // when loaded, draw the image file
240
- image(0, 0, PngImage)
256
+ image(0, 0, myImage)
241
257
  }
242
258
  }
243
259
  ```
244
260
 
245
- To help you load multiple assets (images, fonts, music, etc.), you can I recommend you the [Asset Loader Plugin](https://github.com/litecanvas/plugin-asset-loader).
261
+ [Live Demo](https://litecanvas.js.org?c=eJxVkMFOwzAMhu95CnNqKo1044SQyh1pJyQeIGrTNih1ptpQEOq742RtGVGSg%2F37s38Hz66x%2BGlJl0oFxzB%2Bv4y2d0p1H9iwjwgePesSfhTIqSoI0bZgwScZdFMcwTPB2%2Bs5CxLDjz3UgG6GzBJ0ykjURMzVNQiwfl6Z6axtJSOyHF32IpoaiRcD84WeqirsM5t3MnHqK99EpPzfnx4ezQX7Qi03DtrJzruDJpA%2BitlM70Dfra3Lm2nEpaQwcjbr2gPQEGfgwROMjigtaNOy%2B2J9Oh4gveIseo%2F9dTvGmOLqfQEXyP3vMA8Od34aUfhuW6sPfx1ySAtd7jbsuqLlFwWNf7A%3D)
262
+
263
+ If you need to load multiple assets (images, fonts, music, etc.), I recommend you the [Asset Loader Plugin](https://github.com/litecanvas/plugin-asset-loader).
246
264
 
247
265
  ### Keyboard
248
266
 
@@ -259,9 +277,7 @@ function update() {
259
277
  }
260
278
 
261
279
  // Returns the last key pressed in your keyboard.
262
- const key = lastkey()
263
-
264
- console.log(key)
280
+ let key = lastkey()
265
281
  }
266
282
  ```
267
283
 
@@ -325,7 +341,7 @@ You can find a complete list of everything litecanvas has to offer on our [cheat
325
341
 
326
342
  Try some demos in the playground:
327
343
 
328
- - [Bouncing Ball](https://litecanvas.js.org?c=eJxtkkFugzAQRfecYpaGOMFJW6lVQhddcQPWlm0iSy4gY0hRkrt3ADc4SRdI9nzm%2FzcMRjlo6lY7XVeQQa8E2TJG8YlpBHg3tdBuGM9Hy3t%2FtFzqrsX3X9%2BjyGinBK963pI4isquEpOZrrQjMZwDFx9QJDuap7t4v5h6hdEcq9fApWskd4pINzv9oW5%2BYJXdfPGWgHShPtzpw6wHKLOBT1%2F6g45AXtrTFL5qZFNQlyUoeVQtVnUJZCFb%2Ba%2FzCQVcLgHy2gsHYPM0dzhJBuvt%2Fp8q23y8TeVg%2BAyE4d9NkEr9UigUPifGpusjHc4FN778CWOYMJ4TxwXl3neyDXckLT%2F5TQvTEjYtVmgrSm1MiLjYUQ%2F78rDu8SfAUYbZzSrX2QrOYwUjfwF7%2FdPj)
344
+ - [Bouncing Ball](https://litecanvas.js.org?c=eJxtksFygyAQhu8%2BxR7VmGjSdqZNag89%2BQaeGcAMM1QdRBsm8d27Co3Y9MAM7M%2F%2B%2By2L5BraphNaNDXkMHAa7rMswRUlAeBZNlRoM%2B3PigxuqwgTfYf3n1%2BDQArNKakH0oVREFR9TWczUQsdRnD1XDDhejmW8SExxyI9jKfF1UrZJGB49Hz6lhHNQ6at1y%2Fs7gKb%2FO6MpxiY9nWz0o3VPRhr4Mov%2BV6GJy%2FpaQqfDbJxaKoKODvzDqOignAh27j3%2BYASbjcPeeuEd8hsNyucOIft%2FvRPNNu9vcxhr%2FkcqCRfrVc1cWNJoHR1Ikwa%2F9JhX3DnKx4wzIzxWHGaUOF8Z1t%2FRkyRbzdrKrswi6YuqFC0ElL6iItd4mCfovW4px%2BIrRjrprjuVY1fAyNY8gdFptV%2F)
329
345
  - [Scroller](https://litecanvas.js.org?c=eJxVUM1SgzAQvvMU68FpAhFDLVpH%2BxbOcOj0ECGUzATSIYsyOn13NwXRHpJNvv1%2BkrUGdam6D%2BUZj6J66Eo0rgPTGQTG4TsCaLX36qhhB6tC29K1GtCBXYQ3KyKN1C6onpSl034jcvEonsRWPItMiiw7ROd%2F9sOpUqiBVThFePMV%2FIv7OSu1ujtiQ52G4FxeAu528CAlxFDhHNiqkY0C1pJfuVe9%2Bvx9fGk9ozZA7XpgViMYEsoXKq9wnUZYkkwqgMBEYr4lTKZZbPiC0kLdh%2FRZ7Yd3jz0zArI%2FVumsCySax762zvUM4%2FyWbnMYP1yoqEcMn2dh4wvExsTEAYpluhXQJGuZeNORx4bHjZjfIKaUIDvTAH4AEIGEgw%3D%3D)
330
346
  - [3D projection](https://litecanvas.js.org?c=eJyNVcuS2jAQvPsrJofUykE8wykJySlVyRckuy4fZGwWgZEpWQSWFP%2BeGUlgizVLqgyyprul1mgkl9IUc6H%2BiJrFUbTYqbmRlQKppGEx%2FI0AanksYAa%2F4Cv8gG%2F4G8IUPmEfW4S3uloVTjSDBAMAyZjDCJ%2BUuy6%2Bj8MuRWw3pREqqUzdqPs0I4d2k3IYDiE5cHjhcEwdr4vWRjqA%2Fj2kRQgHuwm8jvc7gbTJVZHTYikg1HNJyR1Fp1byd9tcmILlxm2AI%2FWQNRiNIwwsKg2sLAxIkn7G5otP4qAs1LNZYqjXc2KAeaVqgzhl2LImG2G0PLDBYOBUiUxjS6UxdWWEc4i0za5kNoC2fjNrJOY0luPf5D5euJ5yh%2F%2FUzb%2FkCx02oqbiAvopyGGuxd4X8Lys2SimxG139ZIRt5Sq2MvcLNmEukYLVZeUc6rqCbdFbpGuVE%2Bv06vQDZMcmIQejGN4D1PeeI9DGlIQZS1u7EK3BBxkB%2BFkD84WV9Ne9VkkOGTc14QzSitmfrtFmozoOTMoMA4C2TUjuzA%2BhjP6PaEJfbZtuYl5VVLBCdT4omwwXe0tdg1kXpR1iDIvys4AInIBzE30bjZzjBjMElvA7YXvWleaPfzEy62UObiqL%2BoHWwpuVF3U56PYtdPWabDbKHC1mLhL6KJaOdUKVXYZ%2BNqo3Mmqdxt71F3kolw75Zrmc8p1WwlW16NUyjRZp%2FAB87NOk1XqCSffOmsYx%2FFQEp2xEy1XF2anFXGC3WvOtha5xEPg5vXsm7f5vKovAns1qla3TQyRUDby12KXn8e3%2FFwNE85y45tz7TE084aTp%2F93cisP95Pw6qPYdhLc2f4LGJpJDngukxf6O6Yk%2FgdWEko3)
331
347
  - [Rendering Benchmark](https://litecanvas.js.org?c=eJylVVtP2zAUfs%2BvsLpJSSCkSS9QurYTQkx7AAkB0x6qariJ21q4See40Az633dsp8G9UNDmB1Kf853j71yJ0iQTKEoTwVOWoS6K02g%2BJYnwI06wIBeMyJtjx%2FTRdq0V0KdJQvj3u6tLMLm3OqDtWQhOJwN9Mu6dTdN5ItqdanHXSowmnIy6la9YqbthEFR68KdTxW8gmhLR3IcAc%2Bnk4W1ETSFqexANhWjsQbQUorUHER5rIsd7MHVNpV5w6VRV3mT2UCZyRrqVWZpRQdOkjThhWNBHUil8RTh5xBmicbcSVXqdqr6vnNxbZeGGaZz7eDYjSeys6uVaG3r1nB%2BlLOVQQftTGIb2TsgQRw9jDuzj8xI8Go1sy2JEoDlnIEnIE%2Fpxc%2BmwNMKSvOspypmABgL1s7rJE6mmQIdg5mcE82hyjTmeZv6YQIvpJNkuenlBUFWvNHuisZi0QVZrvAonhI4n4KzRKpBLoEQF0Xlx9KOFpWLiq4vGroy1Qt%2B0Rpu3IcrItpaQNw3JZpyqYGaYwjgoaFjz1r79kpvtw2nAkV%2Fb25BjvEMeBFIeBFvymvRT25CfwKnDacJZkx8fn9TDsN4cDj8mf8OPfPcUzua7kudpq3W6zdP3pXxXvNKPIR%2Foz2tTZBFmpI3qqypCzkfzJJKNhGhCheMW4G%2FXt7%2BuLu4ubvyYZjOGc0fwOQF42Wx%2BRv%2FIIpk102XXoGoVQcMJGjGC1H6DN3TV1RqcFbqs6OkzzuER7Uy1rmuAYyqHoR966CgcKPkIxsORU0FBEXyBTwcZxiA4PHTXpkH6WQCYY5hVFx2YjYqOjJg2bPJtG93D%2B4z68cJDcT6QrEuVPMoTxFGX3iCsvhRQJ%2FBQ6A68f4EOyl9lSvtUPvyMgEMONBQVKLYuuVFBswblb8vIO6PJg%2Fl%2F6vec8PyWMBKJlDv3%2FfWN%2B%2FnZqMCyMrjXJaQjqBQ4Mssh78XSG8Ha%2FKkTCvtumLLYLpgujeaMOX4qmxNa65zBSkNiQoolokmzzAnc7fbDCZ3u67%2BNbPxHg3Ezk1AGq9Rzf4GOoJP8eGHI8kKWvwJluiS4gwLT%2F8oHkNmQxQt00IXBKMVLRFhGCkf5bkf5Lkf5pqNtVofm8PfMGdpN9t0h%2B1gMO5%2FVY7g7tvcH9QMxT%2FGYyKg96dRb23Ru2aN%2FASaYns8%3D)
package/dist/dist.dev.js CHANGED
@@ -32,7 +32,7 @@
32
32
  };
33
33
 
34
34
  // src/version.js
35
- var version = "0.99.0";
35
+ var version = "0.100.1";
36
36
 
37
37
  // src/index.js
38
38
  function litecanvas(settings = {}) {
@@ -50,7 +50,7 @@
50
50
  keyboardEvents: true
51
51
  };
52
52
  settings = Object.assign(defaults, settings);
53
- let _initialized = false, _paused = true, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
53
+ let _initialized = false, _paused = true, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _fontLineHeight = 1.2, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
54
54
  const instance = {
55
55
  /** @type {number} */
56
56
  W: 0,
@@ -545,7 +545,7 @@
545
545
  },
546
546
  /** TEXT RENDERING API */
547
547
  /**
548
- * Draw text
548
+ * Draw text. You can use `\n` to break lines.
549
549
  *
550
550
  * @param {number} x
551
551
  * @param {number} y
@@ -566,7 +566,20 @@
566
566
  );
567
567
  _ctx.font = `${fontStyle} ${_fontSize}px ${_fontFamily}`;
568
568
  _ctx.fillStyle = getColor(color);
569
- _ctx.fillText(message, ~~x, ~~y);
569
+ const messages = ("" + message).split("\n");
570
+ for (let i = 0; i < messages.length; i++) {
571
+ _ctx.fillText(messages[i], ~~x, ~~y + _fontSize * _fontLineHeight * i);
572
+ }
573
+ },
574
+ /**
575
+ * Sets the height ratio of the text lines based on current text size.
576
+ *
577
+ * Default = `1.2`
578
+ *
579
+ * @param value
580
+ */
581
+ textgap(value) {
582
+ _fontLineHeight = value;
570
583
  },
571
584
  /**
572
585
  * Set the font family
@@ -619,10 +632,10 @@
619
632
  * @param {number} y
620
633
  * @param {CanvasImageSource} source
621
634
  */
622
- image(x, y, source) {
635
+ image(x, y, source2) {
623
636
  DEV: assert(isNumber(x), "[litecanvas] image() 1st param must be a number");
624
637
  DEV: assert(isNumber(y), "[litecanvas] image() 2nd param must be a number");
625
- _ctx.drawImage(source, ~~x, ~~y);
638
+ _ctx.drawImage(source2, ~~x, ~~y);
626
639
  },
627
640
  /**
628
641
  * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number (0-9 or a-z) or a dot.
@@ -1031,7 +1044,9 @@
1031
1044
  // 11
1032
1045
  _fontFamily,
1033
1046
  // 12
1034
- _colorPaletteState
1047
+ _colorPaletteState,
1048
+ // 13
1049
+ _fontLineHeight
1035
1050
  ];
1036
1051
  const data = { index, value: internals[index] };
1037
1052
  instance.emit("stat", data);
@@ -1091,13 +1106,6 @@
1091
1106
  instance[k] = math[k];
1092
1107
  }
1093
1108
  function init() {
1094
- const source = settings.loop ? settings.loop : root;
1095
- for (const event of _coreEvents.split(",")) {
1096
- DEV: if (root === source && source[event]) {
1097
- console.info(`[litecanvas] using window.${event}()`);
1098
- }
1099
- if (source[event]) instance.listen(event, source[event]);
1100
- }
1101
1109
  if (settings.autoscale) {
1102
1110
  on(root, "resize", resizeCanvas);
1103
1111
  }
@@ -1433,6 +1441,13 @@
1433
1441
  DEV: console.info(`[litecanvas] version ${version} started`);
1434
1442
  DEV: console.debug(`[litecanvas] litecanvas() options =`, settings);
1435
1443
  setupCanvas();
1444
+ const source = settings.loop ? settings.loop : root;
1445
+ for (const event of _coreEvents.split(",")) {
1446
+ DEV: if (root === source && source[event]) {
1447
+ console.info(`[litecanvas] using window.${event}()`);
1448
+ }
1449
+ if (source[event]) instance.listen(event, source[event]);
1450
+ }
1436
1451
  if ("loading" === document.readyState) {
1437
1452
  on(root, "DOMContentLoaded", () => raf(init));
1438
1453
  } else {
package/dist/dist.js CHANGED
@@ -42,7 +42,7 @@
42
42
  keyboardEvents: true
43
43
  };
44
44
  settings = Object.assign(defaults, settings);
45
- let _initialized = false, _paused = true, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
45
+ let _initialized = false, _paused = true, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _fontLineHeight = 1.2, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
46
46
  const instance = {
47
47
  /** @type {number} */
48
48
  W: 0,
@@ -363,7 +363,7 @@
363
363
  },
364
364
  /** TEXT RENDERING API */
365
365
  /**
366
- * Draw text
366
+ * Draw text. You can use `\n` to break lines.
367
367
  *
368
368
  * @param {number} x
369
369
  * @param {number} y
@@ -374,7 +374,20 @@
374
374
  text(x, y, message, color = 3, fontStyle = "normal") {
375
375
  _ctx.font = `${fontStyle} ${_fontSize}px ${_fontFamily}`;
376
376
  _ctx.fillStyle = getColor(color);
377
- _ctx.fillText(message, ~~x, ~~y);
377
+ const messages = ("" + message).split("\n");
378
+ for (let i = 0; i < messages.length; i++) {
379
+ _ctx.fillText(messages[i], ~~x, ~~y + _fontSize * _fontLineHeight * i);
380
+ }
381
+ },
382
+ /**
383
+ * Sets the height ratio of the text lines based on current text size.
384
+ *
385
+ * Default = `1.2`
386
+ *
387
+ * @param value
388
+ */
389
+ textgap(value) {
390
+ _fontLineHeight = value;
378
391
  },
379
392
  /**
380
393
  * Set the font family
@@ -412,8 +425,8 @@
412
425
  * @param {number} y
413
426
  * @param {CanvasImageSource} source
414
427
  */
415
- image(x, y, source) {
416
- _ctx.drawImage(source, ~~x, ~~y);
428
+ image(x, y, source2) {
429
+ _ctx.drawImage(source2, ~~x, ~~y);
417
430
  },
418
431
  /**
419
432
  * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number (0-9 or a-z) or a dot.
@@ -719,7 +732,9 @@
719
732
  // 11
720
733
  _fontFamily,
721
734
  // 12
722
- _colorPaletteState
735
+ _colorPaletteState,
736
+ // 13
737
+ _fontLineHeight
723
738
  ];
724
739
  const data = { index, value: internals[index] };
725
740
  instance.emit("stat", data);
@@ -774,10 +789,6 @@
774
789
  instance[k] = math[k];
775
790
  }
776
791
  function init() {
777
- const source = settings.loop ? settings.loop : root;
778
- for (const event of _coreEvents.split(",")) {
779
- if (source[event]) instance.listen(event, source[event]);
780
- }
781
792
  if (settings.autoscale) {
782
793
  on(root, "resize", resizeCanvas);
783
794
  }
@@ -1076,6 +1087,10 @@
1076
1087
  root.ENGINE = instance;
1077
1088
  }
1078
1089
  setupCanvas();
1090
+ const source = settings.loop ? settings.loop : root;
1091
+ for (const event of _coreEvents.split(",")) {
1092
+ if (source[event]) instance.listen(event, source[event]);
1093
+ }
1079
1094
  if ("loading" === document.readyState) {
1080
1095
  on(root, "DOMContentLoaded", () => raf(init));
1081
1096
  } else {
package/dist/dist.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(t={}){let a=window,l=Math,n=2*l.PI,i=requestAnimationFrame,o=[],r=(e,t,a)=>{e.addEventListener(t,a,!1),o.push(()=>e.removeEventListener(t,a,!1))},s=e=>e.toLowerCase(),c=e=>e.preventDefault(),f=e=>e.beginPath(),d=(e=>{let t=new AudioContext;return e.zzfxV=1,(a=1,l=.05,n=220,i=0,o=0,r=.1,s=0,c=1,f=0,d=0,u=0,p=0,h=0,m=0,g=0,v=0,w=0,x=1,y=0,b=0,k=0)=>{let E=Math,z=2*E.PI,T=f*=500*z/44100/44100,I=n*=(1-l+2*l*E.random(l=[]))*z/44100,D=0,S=0,A=0,M=1,C=0,L=0,N=0,P=k<0?-1:1,F=z*P*k*2/44100,q=E.cos(F),B=E.sin,H=B(F)/4,O=1+H,V=-2*q/O,W=(1-H)/O,R=(1+P*q)/2/O,G=-(P+q)/O,X=0,Y=0,$=0,j=0;for(i=44100*i+9,y*=44100,o*=44100,r*=44100,w*=44100,d*=500*z/85766121e6,g*=z/44100,u*=z/44100,p*=44100,h=44100*h|0,a*=.3*e.zzfxV,P=i+y+o+r+w|0;A<P;l[A++]=N*a)++L%(100*v|0)||(N=s?1<s?2<s?3<s?B(D*D):E.max(E.min(E.tan(D),1),-1):1-(2*D/z%2+2)%2:1-4*E.abs(E.round(D/z)-D/z):B(D),N=(h?1-b+b*B(z*A/h):1)*(N<0?-1:1)*E.abs(N)**c*(A<i?A/i:A<i+y?1-(A-i)/y*(1-x):A<i+y+o?x:A<P-w?(P-A-w)/r*x:0),N=w?N/2+(w>A?0:(A<P-w?1:(P-A)/w)*l[A-w|0]/2/a):N,k&&(N=j=R*X+G*(X=Y)+R*(Y=N)-W*$-V*($=j))),D+=(F=(n+=f+=d)*E.cos(g*S++))+F*m*B(A**5),M&&++M>p&&(n+=u,I+=u,M=0),!h||++C%h||(n=I,f=T,M=M||1);(a=t.createBuffer(1,P,44100)).getChannelData(0).set(l),(n=t.createBufferSource()).buffer=a,n.connect(t.destination),n.start()}})(a);t=Object.assign({width:null,height:null,autoscale:!0,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0},t);let u=!1,p=!0,h,m=1,g,v=.5,w=1,x,y=1e3/60,b,k,E="sans-serif",z=20,T=Date.now(),I=e,D=[],S=[.5,0,1750,,,.3,1,,,,600,.1],A={},M={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:n,HALF_PI:n/4,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>l.PI/180*e,rad2deg:e=>180/l.PI*e,round:(e,t=0)=>{if(!t)return l.round(e);let a=10**t;return l.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*l.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let o=(e-t)/(a-t)*(n-l)+l;return i?M.clamp(o,l,n):o},norm:(e,t,a)=>M.map(e,t,a,0,1),wave:(e,t,a,l=Math.sin)=>e+(l(a)+1)/2*(t-e),rand:(e=0,t=1)=>(T=(1664525*T+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>l.floor(M.rand(e,t+1)),rseed(e){T=~~e},cls(e){null==e?g.clearRect(0,0,g.canvas.width,g.canvas.height):M.rectfill(0,0,g.canvas.width,g.canvas.height,e)},rect(e,t,a,l,n,i){f(g),g[i?"roundRect":"rect"](~~e-v,~~t-v,~~a+2*v,~~l+2*v,i),M.stroke(n)},rectfill(e,t,a,l,n,i){f(g),g[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),M.fill(n)},circ(e,t,a,l){f(g),g.arc(~~e,~~t,~~a,0,n),M.stroke(l)},circfill(e,t,a,l){f(g),g.arc(~~e,~~t,~~a,0,n),M.fill(l)},oval(e,t,a,l,i){f(g),g.ellipse(~~e,~~t,~~a,~~l,0,0,n),M.stroke(i)},ovalfill(e,t,a,l,i){f(g),g.ellipse(~~e,~~t,~~a,~~l,0,0,n),M.fill(i)},line(e,t,a,l,n){f(g);let i=.5*(0!==v&&~~e==~~a),o=.5*(0!==v&&~~t==~~l);g.moveTo(~~e+i,~~t+o),g.lineTo(~~a+i,~~l+o),M.stroke(n)},linewidth(e){g.lineWidth=~~e,v=.5*(0!=~~e%2)},linedash(e,t=0){g.setLineDash(e),g.lineDashOffset=t},text(e,t,a,l=3,n="normal"){g.font=`${n} ${z}px ${E}`,g.fillStyle=F(l),g.fillText(a,~~e,~~t)},textfont(e){E=e},textsize(e){z=e},textalign(e,t){e&&(g.textAlign=e),t&&(g.textBaseline=t)},image(e,t,a){g.drawImage(a,~~e,~~t)},spr(e,t,a,l,n){let i=n.replace(/\s/g,"");for(let n=0;n<a;n++)for(let o=0;o<l;o++){let l=i[a*o+n]||".";"."!==l&&M.rectfill(e+n,t+o,1,1,parseInt(l,36)||0)}},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,o=g;return n.width=e*i,n.height=t*i,(g=n.getContext("2d")).scale(i,i),a(g),g=o,n.transferToImageBitmap()},ctx:e=>(e&&(g=e),g),push(){g.save()},pop(){g.restore()},translate(e,t){g.translate(~~e,~~t)},scale(e,t){g.scale(e,t||e)},rotate(e){g.rotate(e)},alpha(e){g.globalAlpha=M.clamp(e,0,1)},fill(e){g.fillStyle=F(e),g.fill()},stroke(e){g.strokeStyle=F(e),g.stroke()},clip(e){f(g),e(g),g.clip()},sfx:(e,t=0,l=1)=>!!a.zzfxV&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||S,(0!==t||1!==l)&&((e=e.slice())[0]=l*(e[0]||1),e[10]=~~e[10]+t),d.apply(0,e),e),volume(e){a.zzfxV=e},canvas:()=>h,use(e,t={}){var a=e,l=t;let n=a(M,l);for(let e in n)M.def(e,n[e])},listen:(e,t)=>(A[e=s(e)]=A[e]||new Set,A[e].add(t),()=>A&&A[e].delete(t)),emit(e,t,a,l,n){u&&(P("before:"+(e=s(e)),t,a,l,n),P(e,t,a,l,n),P("after:"+e,t,a,l,n))},pal(t=e){I=t,D=[]},palc(e,t){null==e?D=[]:D[e]=t},def(e,l){M[e]=l,t.global&&(a[e]=l)},timescale(e){w=e},framerate(e){y=1e3/~~e},stat(e){let l={index:e,value:[t,u,y/1e3,m,A,I,S,w,a.zzfxV,T,z,E,D][e]};return M.emit("stat",l),l.value},pause(){p=!0,cancelAnimationFrame(k)},resume(){u&&p&&(p=!1,b=y,x=Date.now(),k=i(L))},paused:()=>p,quit(){for(let e of(M.emit("quit"),M.pause(),u=!1,A={},o))e();if(t.global){for(let e in M)delete a[e];delete a.ENGINE}}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))M[e]=l[e];function C(){let e=t.loop?t.loop:a;for(let t of"init,update,draw,tap,untap,tapping,tapped,resized".split(","))e[t]&&M.listen(t,e[t]);if(t.autoscale&&r(a,"resize",N),t.tapEvents){let e=e=>[(e.pageX-h.offsetLeft)/m,(e.pageY-h.offsetTop)/m],t=new Map,l=(e,a,l)=>{let n={x:a,y:l,xi:a,yi:l,t:Date.now()};return t.set(e,n),n},n=(e,a,n)=>{let i=t.get(e)||l(e);i.x=a,i.y=n},i=e=>e&&Date.now()-e.t<=300,o=!1;r(h,"mousedown",t=>{if(0===t.button){c(t);let[a,n]=e(t);M.emit("tap",a,n,0),l(0,a,n),o=!0}}),r(h,"mouseup",a=>{if(0===a.button){c(a);let l=t.get(0),[n,r]=e(a);i(l)&&M.emit("tapped",l.xi,l.yi,0),M.emit("untap",n,r,0),t.delete(0),o=!1}}),r(a,"mousemove",t=>{c(t);let[a,l]=e(t);M.def("MX",a),M.def("MY",l),o&&(M.emit("tapping",a,l,0),n(0,a,l))}),r(h,"touchstart",t=>{for(let a of(c(t),t.changedTouches)){let[t,n]=e(a);M.emit("tap",t,n,a.identifier+1),l(a.identifier+1,t,n)}}),r(h,"touchmove",t=>{for(let a of(c(t),t.changedTouches)){let[t,l]=e(a);M.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let s=e=>{c(e);let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,l]of t)a.includes(e)||(i(l)&&M.emit("tapped",l.xi,l.yi,e),M.emit("untap",l.x,l.y,e),t.delete(e))};r(h,"touchend",s),r(h,"touchcancel",s),r(a,"blur",()=>{for(let[e,a]of(o=!1,t))M.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,l=(e,t="")=>(t=s(t))?e.has("space"===t?" ":t):e.size>0,n="";r(a,"keydown",a=>{let l=s(a.key);e.has(l)||(e.add(l),t.add(l),n=" "===l?"space":l)}),r(a,"keyup",t=>{e.delete(s(t.key))}),r(a,"blur",()=>e.clear()),M.listen("after:update",()=>t.clear()),M.def("iskeydown",t=>l(e,t)),M.def("iskeypressed",e=>l(t,e)),M.def("lastkey",()=>n)}u=!0,M.emit("init",M),M.resume()}function L(){k=i(L);let e=Date.now(),t=0,a=e-x;for(x=e,b+=a<100?a:y;b>=y;){t++,b-=y;let e=y/1e3*w;M.emit("update",e,t),M.def("T",M.T+e)}t&&(M.emit("draw",g),t>1&&(b=0))}function N(){let e=t.width>0?t.width:innerWidth,a=t.width>0?t.height||t.width:innerHeight;if(M.def("W",e),M.def("H",a),h.width=e,h.height=a,t.autoscale){let n=+t.autoscale;h.style.display||(h.style.display="block",h.style.margin="auto"),m=l.min(innerWidth/e,innerHeight/a),m=n>1&&m>n?n:m,h.style.width=e*m+"px",h.style.height=a*m+"px"}g.imageSmoothingEnabled=!1,M.textalign("start","top"),M.emit("resized",m)}function P(e,t,a,l,n){if(A[e])for(let i of A[e])i(t,a,l,n)}function F(e){return I[~~(D[e]??e)%I.length]}if(t.global){if(a.ENGINE)throw Error("only one global litecanvas is allowed");Object.assign(a,M),a.ENGINE=M}return g=(h=(h="string"==typeof t.canvas?document.querySelector(t.canvas):t.canvas)||document.createElement("canvas")).getContext("2d"),r(h,"click",()=>focus()),N(),h.parentNode||document.body.appendChild(h),h.style.imageRendering="pixelated",h.oncontextmenu=()=>!1,"loading"===document.readyState?r(a,"DOMContentLoaded",()=>i(C)):k=i(C),M}})();
1
+ (()=>{var e=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(t={}){let a,l=window,n=Math,i=2*n.PI,o=requestAnimationFrame,r=[],s=(e,t,a)=>{e.addEventListener(t,a,!1),r.push(()=>e.removeEventListener(t,a,!1))},f=(a=new AudioContext,l.zzfxV=1,(e=1,t=.05,n=220,i=0,o=0,r=.1,s=0,f=1,c=0,d=0,u=0,p=0,h=0,g=0,m=0,v=0,w=0,x=1,b=0,y=0,k=0)=>{let E=Math,z=2*E.PI,D=c*=500*z/44100/44100,P=n*=(1-t+2*t*E.random(t=[]))*z/44100,T=0,C=0,I=0,L=1,S=0,A=0,M=0,N=k<0?-1:1,F=z*N*k*2/44100,q=E.cos(F),B=E.sin,H=B(F)/4,O=1+H,V=-2*q/O,W=(1-H)/O,R=(1+N*q)/2/O,G=-(N+q)/O,X=0,Y=0,$=0,j=0;for(i=44100*i+9,b*=44100,o*=44100,r*=44100,w*=44100,d*=500*z/85766121e6,m*=z/44100,u*=z/44100,p*=44100,h=44100*h|0,e*=.3*l.zzfxV,N=i+b+o+r+w|0;I<N;t[I++]=M*e)++A%(100*v|0)||(M=s?1<s?2<s?3<s?B(T*T):E.max(E.min(E.tan(T),1),-1):1-(2*T/z%2+2)%2:1-4*E.abs(E.round(T/z)-T/z):B(T),M=(h?1-y+y*B(z*I/h):1)*(M<0?-1:1)*E.abs(M)**f*(I<i?I/i:I<i+b?1-(I-i)/b*(1-x):I<i+b+o?x:I<N-w?(N-I-w)/r*x:0),M=w?M/2+(w>I?0:(I<N-w?1:(N-I)/w)*t[I-w|0]/2/e):M,k&&(M=j=R*X+G*(X=Y)+R*(Y=M)-W*$-V*($=j))),T+=(F=(n+=c+=d)*E.cos(m*C++))+F*g*B(I**5),L&&++L>p&&(n+=u,P+=u,L=0),!h||++S%h||(n=P,c=D,L=L||1);(e=a.createBuffer(1,N,44100)).getChannelData(0).set(t),(n=a.createBufferSource()).buffer=e,n.connect(a.destination),n.start()});t=Object.assign({width:null,height:null,autoscale:!0,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0},t);let c=!1,d=!0,u,p=1,h,g=.5,m=1,v,w=1e3/60,x,b,y="sans-serif",k=20,E=1.2,z=Date.now(),D=e,P=[],T=[.5,0,1750,,,.3,1,,,,600,.1],C={},I={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:i,HALF_PI:i/4,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>n.PI/180*e,rad2deg:e=>180/n.PI*e,round:(e,t=0)=>{if(!t)return n.round(e);let a=10**t;return n.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*n.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let o=(e-t)/(a-t)*(n-l)+l;return i?I.clamp(o,l,n):o},norm:(e,t,a)=>I.map(e,t,a,0,1),wave:(e,t,a,l=Math.sin)=>e+(l(a)+1)/2*(t-e),rand:(e=0,t=1)=>(z=(1664525*z+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>n.floor(I.rand(e,t+1)),rseed(e){z=~~e},cls(e){null==e?h.clearRect(0,0,h.canvas.width,h.canvas.height):I.rectfill(0,0,h.canvas.width,h.canvas.height,e)},rect(e,t,a,l,n,i){h.beginPath(),h[i?"roundRect":"rect"](~~e-g,~~t-g,~~a+2*g,~~l+2*g,i),I.stroke(n)},rectfill(e,t,a,l,n,i){h.beginPath(),h[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),I.fill(n)},circ(e,t,a,l){h.beginPath(),h.arc(~~e,~~t,~~a,0,i),I.stroke(l)},circfill(e,t,a,l){h.beginPath(),h.arc(~~e,~~t,~~a,0,i),I.fill(l)},oval(e,t,a,l,n){h.beginPath(),h.ellipse(~~e,~~t,~~a,~~l,0,0,i),I.stroke(n)},ovalfill(e,t,a,l,n){h.beginPath(),h.ellipse(~~e,~~t,~~a,~~l,0,0,i),I.fill(n)},line(e,t,a,l,n){h.beginPath();let i=.5*(0!==g&&~~e==~~a),o=.5*(0!==g&&~~t==~~l);h.moveTo(~~e+i,~~t+o),h.lineTo(~~a+i,~~l+o),I.stroke(n)},linewidth(e){h.lineWidth=~~e,g=.5*(0!=~~e%2)},linedash(e,t=0){h.setLineDash(e),h.lineDashOffset=t},text(e,t,a,l=3,n="normal"){h.font=`${n} ${k}px ${y}`,h.fillStyle=N(l);let i=(""+a).split("\n");for(let a=0;a<i.length;a++)h.fillText(i[a],~~e,~~t+k*E*a)},textgap(e){E=e},textfont(e){y=e},textsize(e){k=e},textalign(e,t){e&&(h.textAlign=e),t&&(h.textBaseline=t)},image(e,t,a){h.drawImage(a,~~e,~~t)},spr(e,t,a,l,n){let i=n.replace(/\s/g,"");for(let n=0;n<a;n++)for(let o=0;o<l;o++){let l=i[a*o+n]||".";"."!==l&&I.rectfill(e+n,t+o,1,1,parseInt(l,36)||0)}},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,o=h;return n.width=e*i,n.height=t*i,(h=n.getContext("2d")).scale(i,i),a(h),h=o,n.transferToImageBitmap()},ctx:e=>(e&&(h=e),h),push(){h.save()},pop(){h.restore()},translate(e,t){h.translate(~~e,~~t)},scale(e,t){h.scale(e,t||e)},rotate(e){h.rotate(e)},alpha(e){h.globalAlpha=I.clamp(e,0,1)},fill(e){h.fillStyle=N(e),h.fill()},stroke(e){h.strokeStyle=N(e),h.stroke()},clip(e){h.beginPath(),e(h),h.clip()},sfx:(e,t=0,a=1)=>!!l.zzfxV&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||T,(0!==t||1!==a)&&((e=e.slice())[0]=a*(e[0]||1),e[10]=~~e[10]+t),f.apply(0,e),e),volume(e){l.zzfxV=e},canvas:()=>u,use(e,t={}){var a=e,l=t;let n=a(I,l);for(let e in n)I.def(e,n[e])},listen:(e,t)=>(C[e=e.toLowerCase()]=C[e]||new Set,C[e].add(t),()=>C&&C[e].delete(t)),emit(e,t,a,l,n){c&&(M("before:"+(e=e.toLowerCase()),t,a,l,n),M(e,t,a,l,n),M("after:"+e,t,a,l,n))},pal(t=e){D=t,P=[]},palc(e,t){null==e?P=[]:P[e]=t},def(e,a){I[e]=a,t.global&&(l[e]=a)},timescale(e){m=e},framerate(e){w=1e3/~~e},stat(e){let a={index:e,value:[t,c,w/1e3,p,C,D,T,m,l.zzfxV,z,k,y,P,E][e]};return I.emit("stat",a),a.value},pause(){d=!0,cancelAnimationFrame(b)},resume(){c&&d&&(d=!1,x=w,v=Date.now(),b=o(S))},paused:()=>d,quit(){for(let e of(I.emit("quit"),I.pause(),c=!1,C={},r))e();if(t.global){for(let e in I)delete l[e];delete l.ENGINE}}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))I[e]=n[e];function L(){if(t.autoscale&&s(l,"resize",A),t.tapEvents){let e=e=>[(e.pageX-u.offsetLeft)/p,(e.pageY-u.offsetTop)/p],t=new Map,a=(e,a,l)=>{let n={x:a,y:l,xi:a,yi:l,t:Date.now()};return t.set(e,n),n},n=(e,l,n)=>{let i=t.get(e)||a(e);i.x=l,i.y=n},i=e=>e&&Date.now()-e.t<=300,o=!1;s(u,"mousedown",t=>{if(0===t.button){t.preventDefault();let[l,n]=e(t);I.emit("tap",l,n,0),a(0,l,n),o=!0}}),s(u,"mouseup",a=>{if(0===a.button){a.preventDefault();let l=t.get(0),[n,r]=e(a);i(l)&&I.emit("tapped",l.xi,l.yi,0),I.emit("untap",n,r,0),t.delete(0),o=!1}}),s(l,"mousemove",t=>{t.preventDefault();let[a,l]=e(t);I.def("MX",a),I.def("MY",l),o&&(I.emit("tapping",a,l,0),n(0,a,l))}),s(u,"touchstart",t=>{for(let l of(t.preventDefault(),t.changedTouches)){let[t,n]=e(l);I.emit("tap",t,n,l.identifier+1),a(l.identifier+1,t,n)}}),s(u,"touchmove",t=>{for(let a of(t.preventDefault(),t.changedTouches)){let[t,l]=e(a);I.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let r=e=>{e.preventDefault();let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,l]of t)a.includes(e)||(i(l)&&I.emit("tapped",l.xi,l.yi,e),I.emit("untap",l.x,l.y,e),t.delete(e))};s(u,"touchend",r),s(u,"touchcancel",r),s(l,"blur",()=>{for(let[e,a]of(o=!1,t))I.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,a=(e,t="")=>(t=t.toLowerCase())?e.has("space"===t?" ":t):e.size>0,n="";s(l,"keydown",a=>{let l=a.key.toLowerCase();e.has(l)||(e.add(l),t.add(l),n=" "===l?"space":l)}),s(l,"keyup",t=>{e.delete(t.key.toLowerCase())}),s(l,"blur",()=>e.clear()),I.listen("after:update",()=>t.clear()),I.def("iskeydown",t=>a(e,t)),I.def("iskeypressed",e=>a(t,e)),I.def("lastkey",()=>n)}c=!0,I.emit("init",I),I.resume()}function S(){b=o(S);let e=Date.now(),t=0,a=e-v;for(v=e,x+=a<100?a:w;x>=w;){t++,x-=w;let e=w/1e3*m;I.emit("update",e,t),I.def("T",I.T+e)}t&&(I.emit("draw",h),t>1&&(x=0))}function A(){let e=t.width>0?t.width:innerWidth,a=t.width>0?t.height||t.width:innerHeight;if(I.def("W",e),I.def("H",a),u.width=e,u.height=a,t.autoscale){let l=+t.autoscale;u.style.display||(u.style.display="block",u.style.margin="auto"),p=n.min(innerWidth/e,innerHeight/a),p=l>1&&p>l?l:p,u.style.width=e*p+"px",u.style.height=a*p+"px"}h.imageSmoothingEnabled=!1,I.textalign("start","top"),I.emit("resized",p)}function M(e,t,a,l,n){if(C[e])for(let i of C[e])i(t,a,l,n)}function N(e){return D[~~(P[e]??e)%D.length]}if(t.global){if(l.ENGINE)throw Error("only one global litecanvas is allowed");Object.assign(l,I),l.ENGINE=I}h=(u=(u="string"==typeof t.canvas?document.querySelector(t.canvas):t.canvas)||document.createElement("canvas")).getContext("2d"),s(u,"click",()=>focus()),A(),u.parentNode||document.body.appendChild(u),u.style.imageRendering="pixelated",u.oncontextmenu=()=>!1;let F=t.loop?t.loop:l;for(let e of"init,update,draw,tap,untap,tapping,tapped,resized".split(","))F[e]&&I.listen(e,F[e]);return"loading"===document.readyState?s(l,"DOMContentLoaded",()=>o(L)):b=o(L),I}})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litecanvas",
3
- "version": "0.99.0",
3
+ "version": "0.100.1",
4
4
  "description": "Lightweight HTML5 canvas 2D game engine suitable for small projects and creative coding. Inspired by PICO-8 and p5.js/Processing.",
5
5
  "license": "MIT",
6
6
  "author": "Luiz Bills <luizbills@pm.me>",
@@ -31,15 +31,13 @@
31
31
  "creative coding"
32
32
  ],
33
33
  "devDependencies": {
34
- "@litecanvas/jsdom-extras": "^2.0.1",
34
+ "@happy-dom/global-registrator": "^18.0.1",
35
35
  "@size-limit/preset-small-lib": "^11.2.0",
36
- "@swc/core": "^1.13.3",
37
- "@types/jsdom": "^21.1.7",
36
+ "@swc/core": "^1.15.0",
38
37
  "ava": "^6.4.1",
39
- "esbuild": "^0.25.8",
38
+ "esbuild": "^0.25.12",
40
39
  "genversion": "^3.2.0",
41
40
  "gzip-size": "^7.0.0",
42
- "jsdom": "^26.1.0",
43
41
  "prettier": "^3.6.2",
44
42
  "sinon": "^21.0.0",
45
43
  "size-limit": "^11.2.0",
@@ -51,7 +49,7 @@
51
49
  "scripts": {
52
50
  "prepare": "npm run build",
53
51
  "prepublishOnly": "npm test",
54
- "test": "ava --timeout=1s --fast-fail --tap | tap-min",
52
+ "test": "ava --tap | tap-min",
55
53
  "test:watch": "ava --watch",
56
54
  "dev": "esbuild src/web.js --bundle --watch --outfile=samples/dist.js --servedir=samples",
57
55
  "build": "npm run genversion && node script/build.js && size-limit",
@@ -66,8 +64,14 @@
66
64
  ],
67
65
  "ava": {
68
66
  "files": [
69
- "tests/**/*.js"
70
- ]
67
+ "tests/**/*.js",
68
+ "!tests/_preload/**/*.js"
69
+ ],
70
+ "require": [
71
+ "./tests/_preload/happy-dom.js"
72
+ ],
73
+ "failFast": true,
74
+ "timeout": "10s"
71
75
  },
72
76
  "size-limit": [
73
77
  {
package/src/index.js CHANGED
@@ -73,6 +73,8 @@ export default function litecanvas(settings = {}) {
73
73
  /** @type {number} */
74
74
  _fontSize = 20,
75
75
  /** @type {number} */
76
+ _fontLineHeight = 1.2,
77
+ /** @type {number} */
76
78
  _rngSeed = Date.now(),
77
79
  /** @type {string[]} */
78
80
  _colorPalette = defaultPalette,
@@ -639,7 +641,7 @@ export default function litecanvas(settings = {}) {
639
641
 
640
642
  /** TEXT RENDERING API */
641
643
  /**
642
- * Draw text
644
+ * Draw text. You can use `\n` to break lines.
643
645
  *
644
646
  * @param {number} x
645
647
  * @param {number} y
@@ -661,7 +663,22 @@ export default function litecanvas(settings = {}) {
661
663
 
662
664
  _ctx.font = `${fontStyle} ${_fontSize}px ${_fontFamily}`
663
665
  _ctx.fillStyle = getColor(color)
664
- _ctx.fillText(message, ~~x, ~~y)
666
+
667
+ const messages = ('' + message).split('\n')
668
+ for (let i = 0; i < messages.length; i++) {
669
+ _ctx.fillText(messages[i], ~~x, ~~y + _fontSize * _fontLineHeight * i)
670
+ }
671
+ },
672
+
673
+ /**
674
+ * Sets the height ratio of the text lines based on current text size.
675
+ *
676
+ * Default = `1.2`
677
+ *
678
+ * @param value
679
+ */
680
+ textgap(value) {
681
+ _fontLineHeight = value
665
682
  },
666
683
 
667
684
  /**
@@ -1197,6 +1214,8 @@ export default function litecanvas(settings = {}) {
1197
1214
  _fontFamily,
1198
1215
  // 12
1199
1216
  _colorPaletteState,
1217
+ // 13
1218
+ _fontLineHeight,
1200
1219
  ]
1201
1220
 
1202
1221
  const data = { index, value: internals[index] }
@@ -1280,15 +1299,6 @@ export default function litecanvas(settings = {}) {
1280
1299
  }
1281
1300
 
1282
1301
  function init() {
1283
- // setup default event listeners
1284
- const source = settings.loop ? settings.loop : root
1285
- for (const event of _coreEvents.split(',')) {
1286
- DEV: if (root === source && source[event]) {
1287
- console.info(`[litecanvas] using window.${event}()`)
1288
- }
1289
- if (source[event]) instance.listen(event, source[event])
1290
- }
1291
-
1292
1302
  // listen window resize event when "autoscale" is enabled
1293
1303
  if (settings.autoscale) {
1294
1304
  on(root, 'resize', resizeCanvas)
@@ -1720,8 +1730,19 @@ export default function litecanvas(settings = {}) {
1720
1730
  DEV: console.info(`[litecanvas] version ${version} started`)
1721
1731
  DEV: console.debug(`[litecanvas] litecanvas() options =`, settings)
1722
1732
 
1733
+ // setup the canvas
1723
1734
  setupCanvas()
1724
1735
 
1736
+ // setup default event listeners
1737
+ const source = settings.loop ? settings.loop : root
1738
+ for (const event of _coreEvents.split(',')) {
1739
+ DEV: if (root === source && source[event]) {
1740
+ console.info(`[litecanvas] using window.${event}()`)
1741
+ }
1742
+ if (source[event]) instance.listen(event, source[event])
1743
+ }
1744
+
1745
+ // init the engine (async)
1725
1746
  if ('loading' === document.readyState) {
1726
1747
  on(root, 'DOMContentLoaded', () => raf(init))
1727
1748
  } else {
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.99.0'
2
+ export const version = '0.100.1'
package/types/global.d.ts CHANGED
@@ -325,7 +325,7 @@ declare global {
325
325
 
326
326
  /** TEXT RENDERING API */
327
327
  /**
328
- * Draw text
328
+ * Draw text. You can use `\n` to break lines.
329
329
  *
330
330
  * @param x
331
331
  * @param y
@@ -355,6 +355,14 @@ declare global {
355
355
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign
356
356
  */
357
357
  function textalign(align: CanvasTextAlign, baseline: CanvasTextBaseline): void
358
+ /**
359
+ * Sets the height ratio of the text lines based on current text size.
360
+ *
361
+ * Default = `1.2`
362
+ *
363
+ * @param value
364
+ */
365
+ function textgap(value: number): void
358
366
 
359
367
  /** BASIC GRAPHICS API */
360
368
  /**
@@ -598,6 +606,7 @@ declare global {
598
606
  * - n = 10: the current font size
599
607
  * - n = 11: the current font family
600
608
  * - n = 12: the current state of the color palette
609
+ * - n = 13: the current font gap
601
610
  * - n = *any other value*: probably returns undefined
602
611
  *
603
612
  * @param index
package/types/types.d.ts CHANGED
@@ -319,7 +319,7 @@ type LitecanvasInstance = {
319
319
 
320
320
  /** TEXT RENDERING API */
321
321
  /**
322
- * Draw text
322
+ * Draw text. You can use `\n` to break lines.
323
323
  *
324
324
  * @param x
325
325
  * @param y
@@ -349,6 +349,14 @@ type LitecanvasInstance = {
349
349
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign
350
350
  */
351
351
  textalign(align: CanvasTextAlign, baseline: CanvasTextBaseline): void
352
+ /**
353
+ * Sets the height ratio of the text lines based on current text size.
354
+ *
355
+ * Default = `1.2`
356
+ *
357
+ * @param value
358
+ */
359
+ textgap(value: number): void
352
360
 
353
361
  /** BASIC GRAPHICS API */
354
362
  /**
@@ -588,6 +596,7 @@ type LitecanvasInstance = {
588
596
  * - n = 10: the current font size
589
597
  * - n = 11: the current font family
590
598
  * - n = 12: the current state of the color palette
599
+ * - n = 13: the current font gap
591
600
  * - n = *any other value*: probably returns undefined
592
601
  *
593
602
  * @param index