@lightningjs/blits 1.7.1 → 1.8.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/CHANGELOG.md +19 -0
- package/docs/essentials/displaying_text.md +53 -1
- package/docs/transitions_animations/transitions.md +21 -1
- package/index.d.ts +819 -14
- package/package.json +5 -4
- package/src/components/FPScounter.js +12 -0
- package/src/engines/L3/element.js +9 -1
- package/src/helpers/deepEqualArray.js +50 -0
- package/src/helpers/deepEqualArray.test.js +127 -0
- package/src/lib/codegenerator/generator.js +5 -6
- package/src/lib/reactivity/reactive.js +15 -8
- package/vite/msdfGenerator.js +89 -10
- package/src/application.d.ts +0 -174
- package/src/component.d.ts +0 -455
- package/src/launch.d.ts +0 -273
- package/src/plugin.d.ts +0 -71
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "c8 npm run test:run",
|
|
16
|
-
"test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules)/**/}*.test.js' | tap-diff",
|
|
16
|
+
"test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
|
|
17
17
|
"lint": "eslint '**/*.js'",
|
|
18
18
|
"lint:fix": "eslint '**/*.js' --fix",
|
|
19
19
|
"prepublishOnly": "node scripts/prepublishOnly.js",
|
|
20
20
|
"postpublish": "node scripts/postpublish.js"
|
|
21
21
|
},
|
|
22
|
+
"types": "./index.d.ts",
|
|
22
23
|
"lint-staged": {
|
|
23
24
|
"*.js": [
|
|
24
25
|
"eslint --fix"
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"tape": "^5.5.0"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@lightningjs/msdf-generator": "^1.0
|
|
51
|
-
"@lightningjs/renderer": "^2.
|
|
51
|
+
"@lightningjs/msdf-generator": "^1.1.0",
|
|
52
|
+
"@lightningjs/renderer": "^2.5.1"
|
|
52
53
|
},
|
|
53
54
|
"repository": {
|
|
54
55
|
"type": "git",
|
|
@@ -141,6 +141,7 @@ export default () =>
|
|
|
141
141
|
props7['h'] = 20
|
|
142
142
|
props7['w'] = 20
|
|
143
143
|
props7['map'] = component.sprite
|
|
144
|
+
|
|
144
145
|
props7['frame'] = component.fps[0]
|
|
145
146
|
|
|
146
147
|
componentType = props7['is'] || 'Sprite'
|
|
@@ -198,6 +199,7 @@ export default () =>
|
|
|
198
199
|
props9['h'] = 20
|
|
199
200
|
props9['w'] = 20
|
|
200
201
|
props9['map'] = component.sprite
|
|
202
|
+
|
|
201
203
|
props9['frame'] = component.fps[1]
|
|
202
204
|
|
|
203
205
|
componentType = props9['is'] || 'Sprite'
|
|
@@ -255,6 +257,7 @@ export default () =>
|
|
|
255
257
|
props11['h'] = 20
|
|
256
258
|
props11['w'] = 20
|
|
257
259
|
props11['map'] = component.sprite
|
|
260
|
+
|
|
258
261
|
props11['frame'] = component.fps[2]
|
|
259
262
|
|
|
260
263
|
componentType = props11['is'] || 'Sprite'
|
|
@@ -386,6 +389,7 @@ export default () =>
|
|
|
386
389
|
props17['h'] = 20
|
|
387
390
|
props17['w'] = 20
|
|
388
391
|
props17['map'] = component.sprite
|
|
392
|
+
|
|
389
393
|
props17['frame'] = component.avgFps[0]
|
|
390
394
|
|
|
391
395
|
componentType = props17['is'] || 'Sprite'
|
|
@@ -443,6 +447,7 @@ export default () =>
|
|
|
443
447
|
props19['h'] = 20
|
|
444
448
|
props19['w'] = 20
|
|
445
449
|
props19['map'] = component.sprite
|
|
450
|
+
|
|
446
451
|
props19['frame'] = component.avgFps[1]
|
|
447
452
|
|
|
448
453
|
componentType = props19['is'] || 'Sprite'
|
|
@@ -500,6 +505,7 @@ export default () =>
|
|
|
500
505
|
props21['h'] = 20
|
|
501
506
|
props21['w'] = 20
|
|
502
507
|
props21['map'] = component.sprite
|
|
508
|
+
|
|
503
509
|
props21['frame'] = component.avgFps[2]
|
|
504
510
|
|
|
505
511
|
componentType = props21['is'] || 'Sprite'
|
|
@@ -632,6 +638,7 @@ export default () =>
|
|
|
632
638
|
props27['h'] = 20
|
|
633
639
|
props27['w'] = 20
|
|
634
640
|
props27['map'] = component.sprite
|
|
641
|
+
|
|
635
642
|
props27['frame'] = component.minFps[0]
|
|
636
643
|
|
|
637
644
|
componentType = props27['is'] || 'Sprite'
|
|
@@ -689,6 +696,7 @@ export default () =>
|
|
|
689
696
|
props29['h'] = 20
|
|
690
697
|
props29['w'] = 20
|
|
691
698
|
props29['map'] = component.sprite
|
|
699
|
+
|
|
692
700
|
props29['frame'] = component.minFps[1]
|
|
693
701
|
|
|
694
702
|
componentType = props29['is'] || 'Sprite'
|
|
@@ -746,6 +754,7 @@ export default () =>
|
|
|
746
754
|
props31['h'] = 20
|
|
747
755
|
props31['w'] = 20
|
|
748
756
|
props31['map'] = component.sprite
|
|
757
|
+
|
|
749
758
|
props31['frame'] = component.minFps[2]
|
|
750
759
|
|
|
751
760
|
componentType = props31['is'] || 'Sprite'
|
|
@@ -876,6 +885,7 @@ export default () =>
|
|
|
876
885
|
props37['h'] = 20
|
|
877
886
|
props37['w'] = 20
|
|
878
887
|
props37['map'] = component.sprite
|
|
888
|
+
|
|
879
889
|
props37['frame'] = component.maxFps[0]
|
|
880
890
|
|
|
881
891
|
componentType = props37['is'] || 'Sprite'
|
|
@@ -933,6 +943,7 @@ export default () =>
|
|
|
933
943
|
props39['h'] = 20
|
|
934
944
|
props39['w'] = 20
|
|
935
945
|
props39['map'] = component.sprite
|
|
946
|
+
|
|
936
947
|
props39['frame'] = component.maxFps[1]
|
|
937
948
|
|
|
938
949
|
componentType = props39['is'] || 'Sprite'
|
|
@@ -990,6 +1001,7 @@ export default () =>
|
|
|
990
1001
|
props41['h'] = 20
|
|
991
1002
|
props41['w'] = 20
|
|
992
1003
|
props41['map'] = component.sprite
|
|
1004
|
+
|
|
993
1005
|
props41['frame'] = component.maxFps[2]
|
|
994
1006
|
|
|
995
1007
|
componentType = props41['is'] || 'Sprite'
|
|
@@ -386,13 +386,21 @@ const Element = {
|
|
|
386
386
|
f,
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
if (transition.start && typeof transition.start === 'function') {
|
|
389
|
+
if (transition.start !== undefined && typeof transition.start === 'function') {
|
|
390
390
|
// fire transition start callback when animation really starts (depending on specified delay)
|
|
391
391
|
f.once('animating', () => {
|
|
392
392
|
transition.start.call(this.component, this, prop, startValue)
|
|
393
393
|
})
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
if (transition.progress !== undefined && typeof transition.progress === 'function') {
|
|
397
|
+
let prevProgress = 0
|
|
398
|
+
f.on('tick', (_node, { progress }) => {
|
|
399
|
+
transition.progress.call(this.component, this, prop, progress, prevProgress)
|
|
400
|
+
prevProgress = progress
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
|
|
396
404
|
f.once('stopped', () => {
|
|
397
405
|
// remove the prop from scheduled transitions
|
|
398
406
|
this.scheduledTransitions[prop] = undefined
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const deepEqualArray = (array1, array2) => {
|
|
19
|
+
if (array1 === array2) return true
|
|
20
|
+
if (array1.length !== array2.length) return false
|
|
21
|
+
|
|
22
|
+
let l = array1.length
|
|
23
|
+
|
|
24
|
+
while (l--) {
|
|
25
|
+
const value1 = array1[l]
|
|
26
|
+
const value2 = array2[l]
|
|
27
|
+
|
|
28
|
+
// values are arrays, deepEqual nested arrays
|
|
29
|
+
if (Array.isArray(value1) && Array.isArray(value2)) {
|
|
30
|
+
if (deepEqualArray(value1, value2) === false) return false
|
|
31
|
+
}
|
|
32
|
+
// values are objects, deepEqual nested objects
|
|
33
|
+
else if (
|
|
34
|
+
typeof value1 === 'object' &&
|
|
35
|
+
value1 !== null &&
|
|
36
|
+
typeof value2 === 'object' &&
|
|
37
|
+
value2 !== null
|
|
38
|
+
) {
|
|
39
|
+
if (deepEqualArray(Object.entries(value1), Object.entries(value2)) === false) return false
|
|
40
|
+
}
|
|
41
|
+
// other type, do strict equal check
|
|
42
|
+
else if (value1 !== value2) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default deepEqualArray
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import test from 'tape'
|
|
19
|
+
import deepEqual from './deepEqualArray.js'
|
|
20
|
+
|
|
21
|
+
test('Type', (assert) => {
|
|
22
|
+
const expected = 'function'
|
|
23
|
+
const actual = typeof deepEqual
|
|
24
|
+
|
|
25
|
+
assert.equal(actual, expected, 'deepEqual should be a function')
|
|
26
|
+
assert.end()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('The same arrays', (assert) => {
|
|
30
|
+
const val1 = ['hello', 'world']
|
|
31
|
+
|
|
32
|
+
assert.true(
|
|
33
|
+
deepEqual(val1, val1),
|
|
34
|
+
'deepEqual should return true if the values are the same reference'
|
|
35
|
+
)
|
|
36
|
+
assert.end()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('Equal simple arrays', (assert) => {
|
|
40
|
+
const val1 = ['hello', 'world']
|
|
41
|
+
const val2 = ['hello', 'world']
|
|
42
|
+
|
|
43
|
+
assert.true(deepEqual(val1, val2), 'deepEqual should return true if simple arrays are equal')
|
|
44
|
+
assert.end()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('Not equal simple arrays', (assert) => {
|
|
48
|
+
const val1 = ['hello', 'world']
|
|
49
|
+
const val2 = ['world', 'hello']
|
|
50
|
+
|
|
51
|
+
assert.false(
|
|
52
|
+
deepEqual(val1, val2),
|
|
53
|
+
'deepEqual should return false if simple arrays are not equal'
|
|
54
|
+
)
|
|
55
|
+
assert.end()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('Not Equal simple arrays (different length)', (assert) => {
|
|
59
|
+
const val1 = ['hello', 'world']
|
|
60
|
+
const val2 = ['hello', 'world', '!']
|
|
61
|
+
|
|
62
|
+
assert.false(deepEqual(val1, val2), 'deepEqual should return false if simple arrays are equal')
|
|
63
|
+
assert.end()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('Equal nested arrays', (assert) => {
|
|
67
|
+
const val1 = ['hello', ['world', '!']]
|
|
68
|
+
const val2 = ['hello', ['world', '!']]
|
|
69
|
+
|
|
70
|
+
assert.true(deepEqual(val1, val2), 'deepEqual should return true if nested arrays are equal')
|
|
71
|
+
assert.end()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('Not equal nested arrays', (assert) => {
|
|
75
|
+
const val1 = ['hello', ['world', '!']]
|
|
76
|
+
const val2 = ['hello', ['world', '!', '?']]
|
|
77
|
+
|
|
78
|
+
assert.false(
|
|
79
|
+
deepEqual(val1, val2),
|
|
80
|
+
'deepEqual should return false if nested arrays are not equal'
|
|
81
|
+
)
|
|
82
|
+
assert.end()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('Equal arrays with objects', (assert) => {
|
|
86
|
+
const val1 = [{ foo: 'bar', bar: 'foo' }, { hello: 'world' }]
|
|
87
|
+
const val2 = [{ foo: 'bar', bar: 'foo' }, { hello: 'world' }]
|
|
88
|
+
|
|
89
|
+
assert.true(
|
|
90
|
+
deepEqual(val1, val2),
|
|
91
|
+
'deepEqual should return true if arrays with objects are equal'
|
|
92
|
+
)
|
|
93
|
+
assert.end()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('Not equal arrays with objects (keys', (assert) => {
|
|
97
|
+
const val1 = [{ foo: 'bar', bar: 'foo' }, { test: 'bla' }]
|
|
98
|
+
const val2 = [{ foo2: 'bar', bar2: 'foo' }, { test: 'bla' }]
|
|
99
|
+
|
|
100
|
+
assert.false(
|
|
101
|
+
deepEqual(val1, val2),
|
|
102
|
+
'deepEqual should return false if arrays with objects are not equal'
|
|
103
|
+
)
|
|
104
|
+
assert.end()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('Equal arrays with mixed values', (assert) => {
|
|
108
|
+
const val1 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 2 }]]]
|
|
109
|
+
const val2 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 2 }]]]
|
|
110
|
+
|
|
111
|
+
assert.true(
|
|
112
|
+
deepEqual(val1, val2),
|
|
113
|
+
'deepEqual should return true if arrays with mixed values are equal'
|
|
114
|
+
)
|
|
115
|
+
assert.end()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('Not Equal arrays with mixed values', (assert) => {
|
|
119
|
+
const val1 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 2 }]]]
|
|
120
|
+
const val2 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 3 }]]]
|
|
121
|
+
|
|
122
|
+
assert.false(
|
|
123
|
+
deepEqual(val1, val2),
|
|
124
|
+
'deepEqual should return true if arrays with mixed values are not equal'
|
|
125
|
+
)
|
|
126
|
+
assert.end()
|
|
127
|
+
})
|
|
@@ -212,12 +212,11 @@ const generateComponentCode = function (
|
|
|
212
212
|
templateObject[key],
|
|
213
213
|
options.component
|
|
214
214
|
)}`)
|
|
215
|
-
renderCode.push(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
)
|
|
215
|
+
renderCode.push(`
|
|
216
|
+
props${counter}['${key.substring(1)}']= ${interpolate(
|
|
217
|
+
templateObject[key],
|
|
218
|
+
options.component
|
|
219
|
+
)}`)
|
|
221
220
|
} else {
|
|
222
221
|
renderCode.push(
|
|
223
222
|
`props${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { ImageTexture } from '@lightningjs/renderer'
|
|
19
19
|
import { track, trigger, pauseTracking, resumeTracking } from './effect.js'
|
|
20
20
|
import symbols from '../symbols.js'
|
|
21
|
+
import deepEqualArray from '../../helpers/deepEqualArray.js'
|
|
21
22
|
|
|
22
23
|
const arrayPatchMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort']
|
|
23
24
|
|
|
@@ -38,7 +39,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
|
|
|
38
39
|
|
|
39
40
|
// if original object is already a proxy, don't create a new one but return the existing one instead
|
|
40
41
|
const existingProxy = proxyMap.get(original)
|
|
41
|
-
if (existingProxy) {
|
|
42
|
+
if (existingProxy !== undefined) {
|
|
42
43
|
return existingProxy
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -50,9 +51,9 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
// handling arrays
|
|
53
|
-
if (Array.isArray(target)) {
|
|
54
|
+
if (Array.isArray(target) === true) {
|
|
54
55
|
if (typeof target[key] === 'object' && target[key] !== null) {
|
|
55
|
-
if (Array.isArray(target[key])) {
|
|
56
|
+
if (Array.isArray(target[key]) === true) {
|
|
56
57
|
track(target, key, global)
|
|
57
58
|
}
|
|
58
59
|
// create a new reactive proxy
|
|
@@ -80,7 +81,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
|
|
|
80
81
|
|
|
81
82
|
// handling objects (but not null values, which have object type in JS)
|
|
82
83
|
if (typeof target[key] === 'object' && target[key] !== null) {
|
|
83
|
-
if (Array.isArray(target[key])) {
|
|
84
|
+
if (Array.isArray(target[key]) === true) {
|
|
84
85
|
track(target, key, global)
|
|
85
86
|
}
|
|
86
87
|
// create a new reactive proxy
|
|
@@ -99,13 +100,19 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
|
|
|
99
100
|
const rawValue = getRaw(value)
|
|
100
101
|
|
|
101
102
|
let result = true
|
|
102
|
-
|
|
103
|
+
const isEqual =
|
|
104
|
+
Array.isArray(rawValue) === true
|
|
105
|
+
? deepEqualArray(oldRawValue, rawValue)
|
|
106
|
+
: oldRawValue === rawValue
|
|
107
|
+
|
|
108
|
+
if (isEqual === false) {
|
|
109
|
+
if (Array.isArray(value) === true) value = getRaw(value).slice(0)
|
|
103
110
|
result = Reflect.set(target, key, value, receiver)
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
if (result &&
|
|
113
|
+
if (result === true && isEqual === false) {
|
|
107
114
|
// if we're assigning an array key directly trigger reactivity on the parent key as well
|
|
108
|
-
if (Array.isArray(target) && key in target) {
|
|
115
|
+
if (Array.isArray(target) === true && key in target === true) {
|
|
109
116
|
trigger(_parent, _key, true)
|
|
110
117
|
}
|
|
111
118
|
trigger(target, key, true)
|
|
@@ -126,7 +133,7 @@ const reactiveDefineProperty = (target, global) => {
|
|
|
126
133
|
if (target[key] !== null && typeof target[key] === 'object') {
|
|
127
134
|
if (Object.getPrototypeOf(target[key]) === Object.prototype) {
|
|
128
135
|
return reactiveDefineProperty(target[key])
|
|
129
|
-
} else if (Array.isArray(target[key])) {
|
|
136
|
+
} else if (Array.isArray(target[key]) === true) {
|
|
130
137
|
for (let i = 0; i < arrayPatchMethods.length - 1; i++) {
|
|
131
138
|
target[key][arrayPatchMethods[i]] = function (v) {
|
|
132
139
|
Array.prototype[arrayPatchMethods[i]].call(this, v)
|
package/vite/msdfGenerator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import * as fs from 'fs'
|
|
3
|
+
import { createHash } from 'crypto'
|
|
3
4
|
import { genFont, setGeneratePaths } from '@lightningjs/msdf-generator'
|
|
4
5
|
import { adjustFont } from '@lightningjs/msdf-generator/adjustFont'
|
|
5
6
|
|
|
@@ -21,6 +22,8 @@ class TaskQueue {
|
|
|
21
22
|
|
|
22
23
|
const fontGenerationQueue = new TaskQueue()
|
|
23
24
|
let config
|
|
25
|
+
let checksumPaths = []
|
|
26
|
+
let checksumData = []
|
|
24
27
|
|
|
25
28
|
export default function () {
|
|
26
29
|
let msdfOutputDir = ''
|
|
@@ -53,10 +56,12 @@ export default function () {
|
|
|
53
56
|
// Attempt to find the font file with supported extensions
|
|
54
57
|
const supportedExtensions = ['ttf', 'otf', 'woff']
|
|
55
58
|
let fontFile = null
|
|
59
|
+
let fontType = null
|
|
56
60
|
for (const fontExt of supportedExtensions) {
|
|
57
61
|
const potentialPath = path.join(fontDir, `${fontName}.${fontExt}`)
|
|
58
62
|
if (fs.existsSync(potentialPath)) {
|
|
59
63
|
fontFile = potentialPath
|
|
64
|
+
fontType = '.' + fontExt
|
|
60
65
|
break
|
|
61
66
|
}
|
|
62
67
|
}
|
|
@@ -70,10 +75,13 @@ export default function () {
|
|
|
70
75
|
const mimeType = ext === 'png' ? 'image/png' : 'application/json'
|
|
71
76
|
|
|
72
77
|
await fontGenerationQueue.enqueue(async () => {
|
|
73
|
-
if (
|
|
74
|
-
|
|
78
|
+
if (isGenerationRequired(fontDir, targetDir, fontName, fontType)) {
|
|
79
|
+
const configFilePath = path.join(fontDir, `${fontName}.config.json`)
|
|
80
|
+
|
|
75
81
|
console.log(`\nGenerating ${targetDir}/${fontName}.msdf.${ext}`)
|
|
76
|
-
await generateSDF(fontFile, path.join(targetDir))
|
|
82
|
+
await generateSDF(fontFile, path.join(targetDir), configFilePath)
|
|
83
|
+
|
|
84
|
+
saveChecksum()
|
|
77
85
|
}
|
|
78
86
|
})
|
|
79
87
|
|
|
@@ -109,14 +117,17 @@ export default function () {
|
|
|
109
117
|
|
|
110
118
|
for (const fontFile of fontFiles) {
|
|
111
119
|
const relativePath = path.relative(publicDir, path.dirname(fontFile))
|
|
120
|
+
const fontExt = path.extname(fontFile)
|
|
112
121
|
const baseName = path.basename(fontFile).replace(/\.(ttf|otf|woff)$/i, '')
|
|
113
|
-
const
|
|
114
|
-
const msdfPngPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.png`)
|
|
122
|
+
const targetDir = path.join(msdfOutputDir, relativePath)
|
|
115
123
|
|
|
116
|
-
// Check
|
|
117
|
-
if (
|
|
124
|
+
// Check MSDF generation is required
|
|
125
|
+
if (isGenerationRequired(path.dirname(fontFile), targetDir, baseName, fontExt)) {
|
|
126
|
+
const configFilePath = path.join(path.dirname(fontFile), `${baseName}.config.json`)
|
|
118
127
|
console.log(`Generating missing MSDF files for ${fontFile}`)
|
|
119
|
-
await generateSDF(fontFile, path.join(msdfOutputDir, relativePath))
|
|
128
|
+
await generateSDF(fontFile, path.join(msdfOutputDir, relativePath), configFilePath)
|
|
129
|
+
|
|
130
|
+
saveChecksum()
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
|
|
@@ -127,11 +138,11 @@ export default function () {
|
|
|
127
138
|
}
|
|
128
139
|
}
|
|
129
140
|
|
|
130
|
-
const generateSDF = async (inputFilePath, outputDirPath) => {
|
|
141
|
+
const generateSDF = async (inputFilePath, outputDirPath, configFilePath) => {
|
|
131
142
|
// Ensure the destination directory exists
|
|
132
143
|
fs.mkdirSync(outputDirPath, { recursive: true })
|
|
133
144
|
|
|
134
|
-
setGeneratePaths(path.dirname(inputFilePath), outputDirPath)
|
|
145
|
+
setGeneratePaths(path.dirname(inputFilePath), outputDirPath, configFilePath)
|
|
135
146
|
|
|
136
147
|
let font = await genFont(path.basename(inputFilePath), 'msdf')
|
|
137
148
|
|
|
@@ -168,6 +179,74 @@ const copyDir = (src, dest) => {
|
|
|
168
179
|
const srcPath = path.join(src, entry.name)
|
|
169
180
|
const destPath = path.join(dest, entry.name)
|
|
170
181
|
|
|
182
|
+
// Should not copy .checksum files to dist directory
|
|
183
|
+
if (srcPath.indexOf('.checksum') !== -1) continue
|
|
184
|
+
|
|
171
185
|
entry.isDirectory() ? copyDir(srcPath, destPath) : fs.copyFileSync(srcPath, destPath) // Copy files
|
|
172
186
|
}
|
|
173
187
|
}
|
|
188
|
+
|
|
189
|
+
// Check font config.json or font file modified since last generation
|
|
190
|
+
const isGenerationRequired = (fontDir, targetDir, fontName, fontExt) => {
|
|
191
|
+
const fontFilePath = path.resolve(fontDir, fontName + fontExt)
|
|
192
|
+
const fontChecksumPath = path.resolve(targetDir, fontName + fontExt + '.checksum')
|
|
193
|
+
|
|
194
|
+
const configJsonPath = path.resolve(fontDir, fontName + '.config.json')
|
|
195
|
+
const configChecksumPath = path.resolve(targetDir, fontName + '.config.checksum')
|
|
196
|
+
|
|
197
|
+
// If font file checksum not exists, should generate msdf
|
|
198
|
+
if (!fs.existsSync(fontChecksumPath)) {
|
|
199
|
+
checksumPaths.push(fontChecksumPath)
|
|
200
|
+
checksumData.push(generateHash(fontFilePath))
|
|
201
|
+
|
|
202
|
+
if (fs.existsSync(configJsonPath)) {
|
|
203
|
+
checksumPaths.push(configChecksumPath)
|
|
204
|
+
checksumData.push(generateHash(configJsonPath))
|
|
205
|
+
}
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// If config.json exists, can be newly added or modified since last generation of msdf
|
|
210
|
+
if (fs.existsSync(configJsonPath)) {
|
|
211
|
+
// If config.json checksum not exists, should generate msdf
|
|
212
|
+
if (!fs.existsSync(configChecksumPath)) {
|
|
213
|
+
checksumPaths.push(configChecksumPath)
|
|
214
|
+
checksumData.push(generateHash(configJsonPath))
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
const isConfigModified = isInputFileModified(configJsonPath, configChecksumPath)
|
|
218
|
+
|
|
219
|
+
// If config.json is modified, should generate msdf
|
|
220
|
+
if (isConfigModified) return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check font file modified, if modified, should generate msdf
|
|
224
|
+
return isInputFileModified(fontFilePath, fontChecksumPath)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const isInputFileModified = (inputFilePath, targetFilePath) => {
|
|
228
|
+
const inputChecksum = generateHash(inputFilePath)
|
|
229
|
+
const targetChecksum = fs.readFileSync(targetFilePath).toString()
|
|
230
|
+
if (inputChecksum !== targetChecksum) {
|
|
231
|
+
checksumPaths.push(targetFilePath)
|
|
232
|
+
checksumData.push(inputChecksum)
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const generateHash = (filePath) => {
|
|
239
|
+
const buffer = fs.readFileSync(filePath)
|
|
240
|
+
return createHash('md5').update(buffer).digest('hex')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const saveChecksum = () => {
|
|
244
|
+
if (checksumPaths.length > 0 && checksumData.length > 0) {
|
|
245
|
+
for (let i = 0; i < checksumPaths.length; i++) {
|
|
246
|
+
// Save checksum
|
|
247
|
+
fs.writeFileSync(checksumPaths[i], checksumData[i])
|
|
248
|
+
}
|
|
249
|
+
checksumData.length = 0
|
|
250
|
+
checksumPaths.length = 0
|
|
251
|
+
}
|
|
252
|
+
}
|