@mapwhit/tilerenderer 0.47.2 → 0.49.0
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/build/min/package.json +1 -1
- package/package.json +3 -2
- package/src/data/array_types.js +169 -0
- package/src/data/bucket/symbol_attributes.js +18 -0
- package/src/data/bucket/symbol_bucket.js +116 -73
- package/src/data/program_configuration.js +18 -10
- package/src/geo/transform.js +17 -7
- package/src/index.js +1 -1
- package/src/render/glyph_atlas.js +3 -6
- package/src/render/image_atlas.js +3 -6
- package/src/render/image_manager.js +41 -41
- package/src/source/rtl_text_plugin.js +1 -0
- package/src/source/source_cache.js +1 -1
- package/src/source/tile.js +6 -5
- package/src/source/worker.js +1 -10
- package/src/style/style.js +4 -19
- package/src/style/style_layer/symbol_style_layer_properties.js +7 -1
- package/src/style-spec/expression/compound_expression.js +30 -16
- package/src/style-spec/expression/definitions/assertion.js +52 -5
- package/src/style-spec/expression/definitions/coercion.js +13 -0
- package/src/style-spec/expression/definitions/comparison.js +193 -0
- package/src/style-spec/expression/definitions/formatted.js +123 -0
- package/src/style-spec/expression/definitions/index.js +11 -62
- package/src/style-spec/expression/definitions/interpolate.js +17 -7
- package/src/style-spec/expression/definitions/literal.js +5 -0
- package/src/style-spec/expression/parsing_context.js +6 -7
- package/src/style-spec/expression/types.js +12 -1
- package/src/style-spec/feature_filter/convert.js +197 -0
- package/src/style-spec/feature_filter/index.js +5 -2
- package/src/style-spec/function/convert.js +78 -100
- package/src/style-spec/reference/v8.json +160 -52
- package/src/symbol/collision_index.js +0 -1
- package/src/symbol/cross_tile_symbol_index.js +12 -7
- package/src/symbol/get_anchors.js +11 -22
- package/src/symbol/mergelines.js +4 -1
- package/src/symbol/placement.js +58 -54
- package/src/symbol/quads.js +7 -6
- package/src/symbol/shaping.js +185 -40
- package/src/symbol/symbol_layout.js +40 -37
- package/src/symbol/transform_text.js +12 -1
- package/src/ui/map.js +8 -25
- package/src/style-spec/expression/definitions/array.js +0 -82
- package/src/style-spec/expression/definitions/equals.js +0 -93
package/src/index.js
CHANGED
|
@@ -43,7 +43,7 @@ module.exports = {
|
|
|
43
43
|
* @param {string} pluginURL URL pointing to the Mapbox RTL text plugin source.
|
|
44
44
|
* @param {Function} callback Called with an error argument if there is an error.
|
|
45
45
|
* @example
|
|
46
|
-
* mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.
|
|
46
|
+
* mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.0/mapbox-gl-rtl-text.js');
|
|
47
47
|
* @see [Add support for right-to-left scripts](https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-rtl-text/)
|
|
48
48
|
*/
|
|
49
49
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
const ShelfPack = require('@mapbox/shelf-pack');
|
|
2
|
-
|
|
3
1
|
const { AlphaImage } = require('../util/image');
|
|
4
2
|
const { register } = require('../util/transfer_registry');
|
|
3
|
+
const potpack = require('potpack');
|
|
5
4
|
|
|
6
5
|
const padding = 1;
|
|
7
6
|
|
|
8
7
|
class GlyphAtlas {
|
|
9
8
|
constructor(stacks) {
|
|
10
9
|
const positions = {};
|
|
11
|
-
const pack = new ShelfPack(0, 0, { autoResize: true });
|
|
12
10
|
const bins = [];
|
|
13
11
|
|
|
14
12
|
for (const stack in stacks) {
|
|
@@ -30,9 +28,8 @@ class GlyphAtlas {
|
|
|
30
28
|
}
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const image = new AlphaImage({ width: pack.w, height: pack.h });
|
|
31
|
+
const { w, h } = potpack(bins);
|
|
32
|
+
const image = new AlphaImage({ width: w ?? 1, height: h ?? 1 });
|
|
36
33
|
|
|
37
34
|
for (const stack in stacks) {
|
|
38
35
|
const glyphs = stacks[stack];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
const ShelfPack = require('@mapbox/shelf-pack');
|
|
2
|
-
|
|
3
1
|
const { RGBAImage } = require('../util/image');
|
|
4
2
|
const { register } = require('../util/transfer_registry');
|
|
3
|
+
const potpack = require('potpack');
|
|
5
4
|
|
|
6
5
|
const padding = 1;
|
|
7
6
|
|
|
@@ -32,7 +31,6 @@ class ImageAtlas {
|
|
|
32
31
|
constructor(icons, patterns) {
|
|
33
32
|
const iconPositions = {};
|
|
34
33
|
const patternPositions = {};
|
|
35
|
-
const pack = new ShelfPack(0, 0, { autoResize: true });
|
|
36
34
|
const bins = [];
|
|
37
35
|
for (const id in icons) {
|
|
38
36
|
const src = icons[id];
|
|
@@ -58,9 +56,8 @@ class ImageAtlas {
|
|
|
58
56
|
patternPositions[id] = new ImagePosition(bin, src);
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const image = new RGBAImage({ width: pack.w, height: pack.h });
|
|
59
|
+
const { w, h } = potpack(bins);
|
|
60
|
+
const image = new RGBAImage({ width: w || 1, height: h || 1 });
|
|
64
61
|
|
|
65
62
|
for (const id in icons) {
|
|
66
63
|
const src = icons[id];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const potpack = require('potpack');
|
|
2
2
|
|
|
3
3
|
const { RGBAImage } = require('../util/image');
|
|
4
4
|
const { ImagePosition } = require('./image_atlas');
|
|
@@ -26,9 +26,8 @@ class ImageManager {
|
|
|
26
26
|
constructor() {
|
|
27
27
|
this.images = {};
|
|
28
28
|
|
|
29
|
-
this.shelfPack = new ShelfPack(64, 64, { autoResize: true });
|
|
30
29
|
this.patterns = {};
|
|
31
|
-
this.atlasImage = new RGBAImage({ width:
|
|
30
|
+
this.atlasImage = new RGBAImage({ width: 1, height: 1 });
|
|
32
31
|
this.dirty = true;
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -56,12 +55,7 @@ class ImageManager {
|
|
|
56
55
|
removeImage(id) {
|
|
57
56
|
assert(this.images[id]);
|
|
58
57
|
delete this.images[id];
|
|
59
|
-
|
|
60
|
-
const pattern = this.patterns[id];
|
|
61
|
-
if (pattern) {
|
|
62
|
-
this.shelfPack.unref(pattern.bin);
|
|
63
|
-
delete this.patterns[id];
|
|
64
|
-
}
|
|
58
|
+
delete this.patterns[id];
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
listImages() {
|
|
@@ -89,10 +83,8 @@ class ImageManager {
|
|
|
89
83
|
// Pattern stuff
|
|
90
84
|
|
|
91
85
|
getPixelSize() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
height: this.shelfPack.h
|
|
95
|
-
};
|
|
86
|
+
const { width, height } = this.atlasImage;
|
|
87
|
+
return { width, height };
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
getPattern(id) {
|
|
@@ -106,36 +98,13 @@ class ImageManager {
|
|
|
106
98
|
return null;
|
|
107
99
|
}
|
|
108
100
|
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const bin = this.shelfPack.packOne(width, height);
|
|
113
|
-
if (!bin) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
this.atlasImage.resize(this.getPixelSize());
|
|
118
|
-
|
|
119
|
-
const src = image.data;
|
|
120
|
-
const dst = this.atlasImage;
|
|
121
|
-
|
|
122
|
-
const x = bin.x + padding;
|
|
123
|
-
const y = bin.y + padding;
|
|
124
|
-
const w = src.width;
|
|
125
|
-
const h = src.height;
|
|
126
|
-
|
|
127
|
-
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });
|
|
128
|
-
|
|
129
|
-
// Add 1 pixel wrapped padding on each side of the image.
|
|
130
|
-
RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x: x, y: y - 1 }, { width: w, height: 1 }); // T
|
|
131
|
-
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x, y: y + h }, { width: w, height: 1 }); // B
|
|
132
|
-
RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y: y }, { width: 1, height: h }); // L
|
|
133
|
-
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y: y }, { width: 1, height: h }); // R
|
|
134
|
-
|
|
135
|
-
this.dirty = true;
|
|
136
|
-
|
|
101
|
+
const w = image.data.width + padding * 2;
|
|
102
|
+
const h = image.data.height + padding * 2;
|
|
103
|
+
const bin = { w, h, x: 0, y: 0 };
|
|
137
104
|
const position = new ImagePosition(bin, image);
|
|
138
105
|
this.patterns[id] = { bin, position };
|
|
106
|
+
this._updatePatternAtlas();
|
|
107
|
+
|
|
139
108
|
return position;
|
|
140
109
|
}
|
|
141
110
|
|
|
@@ -150,6 +119,37 @@ class ImageManager {
|
|
|
150
119
|
|
|
151
120
|
this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
|
|
152
121
|
}
|
|
122
|
+
|
|
123
|
+
_updatePatternAtlas() {
|
|
124
|
+
const bins = [];
|
|
125
|
+
for (const id in this.patterns) {
|
|
126
|
+
bins.push(this.patterns[id].bin);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { w, h } = potpack(bins);
|
|
130
|
+
|
|
131
|
+
const dst = this.atlasImage;
|
|
132
|
+
dst.resize({ width: w ?? 1, height: h ?? 1 });
|
|
133
|
+
|
|
134
|
+
for (const id in this.patterns) {
|
|
135
|
+
const { bin } = this.patterns[id];
|
|
136
|
+
const x = bin.x + padding;
|
|
137
|
+
const y = bin.y + padding;
|
|
138
|
+
const src = this.images[id].data;
|
|
139
|
+
const w = src.width;
|
|
140
|
+
const h = src.height;
|
|
141
|
+
|
|
142
|
+
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });
|
|
143
|
+
|
|
144
|
+
// Add 1 pixel wrapped padding on each side of the image.
|
|
145
|
+
RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x: x, y: y - 1 }, { width: w, height: 1 }); // T
|
|
146
|
+
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x, y: y + h }, { width: w, height: 1 }); // B
|
|
147
|
+
RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y: y }, { width: 1, height: h }); // L
|
|
148
|
+
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y: y }, { width: 1, height: h }); // R
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.dirty = true;
|
|
152
|
+
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
module.exports = ImageManager;
|
|
@@ -47,6 +47,7 @@ function setRTLTextPlugin(url, callback) {
|
|
|
47
47
|
const plugin = {
|
|
48
48
|
applyArabicShaping: null,
|
|
49
49
|
processBidirectionalText: null,
|
|
50
|
+
processStyledBidirectionalText: null,
|
|
50
51
|
isLoaded: function () {
|
|
51
52
|
return (
|
|
52
53
|
foregroundLoadComplete || // Foreground: loaded if the completion callback returned successfully
|
package/src/source/tile.js
CHANGED
|
@@ -101,8 +101,9 @@ class Tile {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
this.queryPadding = 0;
|
|
104
|
-
for (const
|
|
105
|
-
|
|
104
|
+
for (const id in this.buckets) {
|
|
105
|
+
const bucket = this.buckets[id];
|
|
106
|
+
this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket));
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
if (data.imageAtlas) {
|
|
@@ -297,8 +298,8 @@ class Tile {
|
|
|
297
298
|
|
|
298
299
|
const vtLayers = this.latestFeatureIndex.loadVTLayers();
|
|
299
300
|
|
|
300
|
-
for (const
|
|
301
|
-
const bucket = this.buckets[
|
|
301
|
+
for (const id in this.buckets) {
|
|
302
|
+
const bucket = this.buckets[id];
|
|
302
303
|
// Buckets are grouped by common source-layer
|
|
303
304
|
const sourceLayerId = bucket.layers[0]['sourceLayer'] || '_geojsonTileLayer';
|
|
304
305
|
const sourceLayer = vtLayers[sourceLayerId];
|
|
@@ -307,7 +308,7 @@ class Tile {
|
|
|
307
308
|
|
|
308
309
|
bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas?.patternPositions || {});
|
|
309
310
|
if (painter?.style) {
|
|
310
|
-
this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(
|
|
311
|
+
this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket));
|
|
311
312
|
}
|
|
312
313
|
}
|
|
313
314
|
}
|
package/src/source/worker.js
CHANGED
|
@@ -38,6 +38,7 @@ class Worker {
|
|
|
38
38
|
}
|
|
39
39
|
globalRTLTextPlugin['applyArabicShaping'] = rtlTextPlugin.applyArabicShaping;
|
|
40
40
|
globalRTLTextPlugin['processBidirectionalText'] = rtlTextPlugin.processBidirectionalText;
|
|
41
|
+
globalRTLTextPlugin['processStyledBidirectionalText'] = rtlTextPlugin.processStyledBidirectionalText;
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -71,16 +72,6 @@ class Worker {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
/**
|
|
75
|
-
* Load a {@link WorkerSource} script at params.url. The script is run
|
|
76
|
-
* (using importScripts) with `registerWorkerSource` in scope, which is a
|
|
77
|
-
* function taking `(name, workerSourceObject)`.
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
loadWorkerSource(map, params) {
|
|
81
|
-
this.self.importScripts(params.url);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
75
|
loadRTLTextPlugin(map, pluginURL) {
|
|
85
76
|
if (!globalRTLTextPlugin.isLoaded()) {
|
|
86
77
|
this.self.importScripts(pluginURL);
|
package/src/style/style.js
CHANGED
|
@@ -633,6 +633,10 @@ class Style extends Evented {
|
|
|
633
633
|
this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
|
|
634
634
|
return;
|
|
635
635
|
}
|
|
636
|
+
if (feature.id == null || feature.id === '') {
|
|
637
|
+
this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
636
640
|
|
|
637
641
|
sourceCache.setFeatureState(sourceLayer, feature.id, state);
|
|
638
642
|
}
|
|
@@ -767,25 +771,6 @@ class Style extends Evented {
|
|
|
767
771
|
return sourceCache ? querySourceFeatures(sourceCache, params) : [];
|
|
768
772
|
}
|
|
769
773
|
|
|
770
|
-
addSourceType(name, SourceType, callback) {
|
|
771
|
-
if (Style.getSourceType(name)) {
|
|
772
|
-
return callback(new Error(`A source type called "${name}" already exists.`));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
Style.setSourceType(name, SourceType);
|
|
776
|
-
|
|
777
|
-
if (!SourceType.workerSourceURL) {
|
|
778
|
-
return callback(null, null);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
this.dispatcher
|
|
782
|
-
.broadcast('loadWorkerSource', {
|
|
783
|
-
name: name,
|
|
784
|
-
url: SourceType.workerSourceURL
|
|
785
|
-
})
|
|
786
|
-
.then(data => callback(null, data), callback);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
774
|
getLight() {
|
|
790
775
|
return this.light.getLight();
|
|
791
776
|
}
|
|
@@ -19,6 +19,12 @@ const layout = new Properties({
|
|
|
19
19
|
default: false,
|
|
20
20
|
expression: { parameters: ['zoom'] }
|
|
21
21
|
}),
|
|
22
|
+
'symbol-z-order': new DataConstantProperty({
|
|
23
|
+
type: 'enum',
|
|
24
|
+
values: ['viewport-y', 'source'],
|
|
25
|
+
default: 'viewport-y',
|
|
26
|
+
expression: { parameters: ['zoom'] }
|
|
27
|
+
}),
|
|
22
28
|
'icon-allow-overlap': new DataConstantProperty({
|
|
23
29
|
type: 'boolean',
|
|
24
30
|
default: false,
|
|
@@ -106,7 +112,7 @@ const layout = new Properties({
|
|
|
106
112
|
expression: { parameters: ['zoom'] }
|
|
107
113
|
}),
|
|
108
114
|
'text-field': new DataDrivenProperty({
|
|
109
|
-
type: '
|
|
115
|
+
type: 'formatted',
|
|
110
116
|
default: '',
|
|
111
117
|
tokens: true,
|
|
112
118
|
expression: { parameters: ['zoom', 'feature'] }
|
|
@@ -46,20 +46,6 @@ class CompoundExpression {
|
|
|
46
46
|
signature.length === args.length - 1 // correct param count
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
-
// First parse all the args
|
|
50
|
-
const parsedArgs = [];
|
|
51
|
-
for (let i = 1; i < args.length; i++) {
|
|
52
|
-
const arg = args[i];
|
|
53
|
-
let expected;
|
|
54
|
-
if (overloads.length === 1) {
|
|
55
|
-
const params = overloads[0][0];
|
|
56
|
-
expected = Array.isArray(params) ? params[i - 1] : params.type;
|
|
57
|
-
}
|
|
58
|
-
const parsed = context.parse(arg, 1 + parsedArgs.length, expected);
|
|
59
|
-
if (!parsed) return null;
|
|
60
|
-
parsedArgs.push(parsed);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
49
|
let signatureContext = null;
|
|
64
50
|
|
|
65
51
|
for (const [params, evaluate] of overloads) {
|
|
@@ -67,6 +53,27 @@ class CompoundExpression {
|
|
|
67
53
|
// we eventually succeed, we haven't polluted `context.errors`.
|
|
68
54
|
signatureContext = new ParsingContext(context.registry, context.path, null, context.scope);
|
|
69
55
|
|
|
56
|
+
// First parse all the args, potentially coercing to the
|
|
57
|
+
// types expected by this overload.
|
|
58
|
+
const parsedArgs = [];
|
|
59
|
+
let argParseFailed = false;
|
|
60
|
+
for (let i = 1; i < args.length; i++) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
const expectedType = Array.isArray(params) ? params[i - 1] : params.type;
|
|
63
|
+
|
|
64
|
+
const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType);
|
|
65
|
+
if (!parsed) {
|
|
66
|
+
argParseFailed = true;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
parsedArgs.push(parsed);
|
|
70
|
+
}
|
|
71
|
+
if (argParseFailed) {
|
|
72
|
+
// Couldn't coerce args of this overload to expected type, move
|
|
73
|
+
// on to next one.
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
if (Array.isArray(params)) {
|
|
71
78
|
if (params.length !== parsedArgs.length) {
|
|
72
79
|
signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`);
|
|
@@ -92,8 +99,15 @@ class CompoundExpression {
|
|
|
92
99
|
} else {
|
|
93
100
|
const expected = overloads.length ? overloads : availableOverloads;
|
|
94
101
|
const signatures = expected.map(([params]) => stringifySignature(params)).join(' | ');
|
|
95
|
-
const actualTypes =
|
|
96
|
-
|
|
102
|
+
const actualTypes = [];
|
|
103
|
+
// For error message, re-parse arguments without trying to
|
|
104
|
+
// apply any coercions
|
|
105
|
+
for (let i = 1; i < args.length; i++) {
|
|
106
|
+
const parsed = context.parse(args[i], 1 + actualTypes.length);
|
|
107
|
+
if (!parsed) return null;
|
|
108
|
+
actualTypes.push(toString(parsed.type));
|
|
109
|
+
}
|
|
110
|
+
context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`);
|
|
97
111
|
}
|
|
98
112
|
|
|
99
113
|
return null;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
ObjectType,
|
|
5
|
+
ValueType,
|
|
6
|
+
StringType,
|
|
7
|
+
NumberType,
|
|
8
|
+
BooleanType,
|
|
9
|
+
checkSubtype,
|
|
10
|
+
toString,
|
|
11
|
+
array
|
|
12
|
+
} = require('../types');
|
|
4
13
|
const RuntimeError = require('../runtime_error');
|
|
5
14
|
const { typeOf } = require('../values');
|
|
6
15
|
|
|
@@ -20,13 +29,39 @@ class Assertion {
|
|
|
20
29
|
static parse(args, context) {
|
|
21
30
|
if (args.length < 2) return context.error('Expected at least one argument.');
|
|
22
31
|
|
|
32
|
+
let i = 1;
|
|
33
|
+
let type;
|
|
34
|
+
|
|
23
35
|
const name = args[0];
|
|
24
|
-
|
|
36
|
+
if (name === 'array') {
|
|
37
|
+
let itemType;
|
|
38
|
+
if (args.length > 2) {
|
|
39
|
+
const type = args[1];
|
|
40
|
+
if (typeof type !== 'string' || !(type in types) || type === 'object')
|
|
41
|
+
return context.error('The item type argument of "array" must be one of string, number, boolean', 1);
|
|
42
|
+
itemType = types[type];
|
|
43
|
+
i++;
|
|
44
|
+
} else {
|
|
45
|
+
itemType = ValueType;
|
|
46
|
+
}
|
|
25
47
|
|
|
26
|
-
|
|
48
|
+
let N;
|
|
49
|
+
if (args.length > 3) {
|
|
50
|
+
if (args[2] !== null && (typeof args[2] !== 'number' || args[2] < 0 || args[2] !== Math.floor(args[2]))) {
|
|
51
|
+
return context.error('The length argument to "array" must be a positive integer literal', 2);
|
|
52
|
+
}
|
|
53
|
+
N = args[2];
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type = array(itemType, N);
|
|
58
|
+
} else {
|
|
59
|
+
assert(types[name], name);
|
|
60
|
+
type = types[name];
|
|
61
|
+
}
|
|
27
62
|
|
|
28
63
|
const parsed = [];
|
|
29
|
-
for (
|
|
64
|
+
for (; i < args.length; i++) {
|
|
30
65
|
const input = context.parse(args[i], i, ValueType);
|
|
31
66
|
if (!input) return null;
|
|
32
67
|
parsed.push(input);
|
|
@@ -62,7 +97,19 @@ class Assertion {
|
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
serialize() {
|
|
65
|
-
|
|
100
|
+
const type = this.type;
|
|
101
|
+
const serialized = [type.kind];
|
|
102
|
+
if (type.kind === 'array') {
|
|
103
|
+
const itemType = type.itemType;
|
|
104
|
+
if (itemType.kind === 'string' || itemType.kind === 'number' || itemType.kind === 'boolean') {
|
|
105
|
+
serialized.push(itemType.kind);
|
|
106
|
+
const N = type.N;
|
|
107
|
+
if (typeof N === 'number' || this.args.length > 1) {
|
|
108
|
+
serialized.push(N);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return serialized.concat(this.args.map(arg => arg.serialize()));
|
|
66
113
|
}
|
|
67
114
|
}
|
|
68
115
|
|
|
@@ -3,6 +3,7 @@ const assert = require('assert');
|
|
|
3
3
|
const { ColorType, ValueType, NumberType } = require('../types');
|
|
4
4
|
const { Color, validateRGBA } = require('../values');
|
|
5
5
|
const RuntimeError = require('../runtime_error');
|
|
6
|
+
const { Formatted, FormattedSection } = require('./formatted');
|
|
6
7
|
|
|
7
8
|
const types = {
|
|
8
9
|
'to-number': NumberType,
|
|
@@ -65,6 +66,18 @@ class Coercion {
|
|
|
65
66
|
error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`
|
|
66
67
|
);
|
|
67
68
|
}
|
|
69
|
+
if (this.type.kind === 'formatted') {
|
|
70
|
+
let input;
|
|
71
|
+
for (const arg of this.args) {
|
|
72
|
+
input = arg.evaluate(ctx);
|
|
73
|
+
if (typeof input === 'string') {
|
|
74
|
+
return new Formatted([new FormattedSection(input, null, null)]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw new RuntimeError(
|
|
78
|
+
`Could not parse formatted text from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
68
81
|
let value = null;
|
|
69
82
|
for (const arg of this.args) {
|
|
70
83
|
value = arg.evaluate(ctx);
|