@mapwhit/tilerenderer 0.47.1 → 0.48.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 +1 -2
- package/src/data/array_types.js +1 -1
- package/src/data/bucket/circle_bucket.js +1 -1
- package/src/data/bucket/fill_bucket.js +1 -1
- package/src/data/bucket/fill_extrusion_bucket.js +1 -1
- package/src/data/bucket/heatmap_bucket.js +1 -1
- package/src/data/bucket/line_bucket.js +1 -1
- package/src/data/bucket/symbol_bucket.js +26 -12
- package/src/data/dem_data.js +1 -1
- package/src/data/feature_index.js +43 -82
- package/src/data/program_configuration.js +19 -11
- package/src/data/segment.js +2 -2
- package/src/geo/transform.js +4 -2
- package/src/gl/color_mode.js +6 -6
- package/src/index.js +3 -1
- package/src/render/glyph_atlas.js +1 -1
- package/src/render/glyph_manager.js +43 -48
- package/src/render/image_atlas.js +1 -1
- package/src/render/image_manager.js +9 -37
- package/src/source/geojson_source.js +49 -93
- package/src/source/geojson_worker_source.js +33 -134
- package/src/source/image_source.js +9 -14
- package/src/source/load_tilejson.js +27 -34
- package/src/source/raster_dem_tile_source.js +27 -40
- package/src/source/raster_tile_source.js +53 -62
- package/src/source/rtl_text_plugin.js +3 -1
- package/src/source/source_cache.js +23 -21
- package/src/source/source_state.js +17 -26
- package/src/source/tile.js +6 -5
- package/src/source/tile_id.js +1 -1
- package/src/source/vector_tile_source.js +56 -73
- package/src/source/vector_tile_worker_source.js +20 -85
- package/src/source/worker.js +37 -103
- package/src/source/worker_tile.js +39 -84
- package/src/style/load_sprite.js +14 -17
- package/src/style/properties.js +1 -1
- package/src/style/style.js +22 -37
- package/src/style/style_layer/symbol_style_layer_properties.js +1 -1
- package/src/style/style_layer_index.js +17 -23
- package/src/style-spec/expression/compound_expression.js +30 -16
- 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 +10 -60
- 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 +4 -0
- package/src/style-spec/expression/types.js +12 -1
- package/src/style-spec/feature_filter/index.js +1 -1
- package/src/style-spec/reference/v8.json +120 -49
- package/src/symbol/anchor.js +1 -1
- package/src/symbol/collision_index.js +23 -16
- package/src/symbol/get_anchors.js +11 -22
- package/src/symbol/grid_index.js +176 -182
- package/src/symbol/mergelines.js +51 -48
- package/src/symbol/opacity_state.js +1 -1
- package/src/symbol/placement.js +8 -2
- package/src/symbol/quads.js +7 -6
- package/src/symbol/shaping.js +185 -40
- package/src/symbol/symbol_layout.js +9 -6
- package/src/symbol/transform_text.js +12 -1
- package/src/ui/camera.js +82 -85
- package/src/ui/map.js +13 -57
- package/src/util/actor.js +46 -42
- package/src/util/browser.js +6 -0
- package/src/util/dictionary_coder.js +13 -21
- package/src/util/dispatcher.js +14 -17
- package/src/util/image.js +1 -1
- package/src/util/loader/image.js +11 -11
- package/src/util/polyfill.js +16 -0
- package/src/util/task_queue.js +39 -43
- package/src/util/transfer_registry.js +167 -0
- package/src/util/web_worker_transfer.js +5 -190
- package/src/source/raster_dem_tile_worker_source.js +0 -26
- package/src/style-spec/expression/definitions/equals.js +0 -93
|
@@ -5,54 +5,48 @@ const featureFilter = require('../style-spec/feature_filter');
|
|
|
5
5
|
const groupByLayout = require('../style-spec/group_by_layout');
|
|
6
6
|
|
|
7
7
|
class StyleLayerIndex {
|
|
8
|
+
#layerConfigs = {};
|
|
9
|
+
#layers = {};
|
|
10
|
+
|
|
8
11
|
constructor(layerConfigs) {
|
|
9
12
|
if (layerConfigs) {
|
|
10
|
-
this.
|
|
13
|
+
this.update(layerConfigs);
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
replace(layerConfigs) {
|
|
15
|
-
this
|
|
16
|
-
this
|
|
17
|
-
this.update(layerConfigs
|
|
18
|
+
this.#layerConfigs = {};
|
|
19
|
+
this.#layers = {};
|
|
20
|
+
this.update(layerConfigs);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
update(layerConfigs, removedIds) {
|
|
23
|
+
update(layerConfigs, removedIds = []) {
|
|
21
24
|
for (const layerConfig of layerConfigs) {
|
|
22
|
-
this
|
|
25
|
+
this.#layerConfigs[layerConfig.id] = layerConfig;
|
|
23
26
|
|
|
24
|
-
const layer = (this
|
|
27
|
+
const layer = (this.#layers[layerConfig.id] = createStyleLayer(layerConfig));
|
|
25
28
|
layer._featureFilter = featureFilter(layer.filter);
|
|
26
29
|
}
|
|
27
30
|
for (const id of removedIds) {
|
|
28
|
-
delete this
|
|
29
|
-
delete this
|
|
31
|
+
delete this.#layerConfigs[id];
|
|
32
|
+
delete this.#layers[id];
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
this.familiesBySource = {};
|
|
33
36
|
|
|
34
|
-
const groups = groupByLayout(values(this
|
|
37
|
+
const groups = groupByLayout(values(this.#layerConfigs));
|
|
35
38
|
|
|
36
39
|
for (const layerConfigs of groups) {
|
|
37
|
-
const layers = layerConfigs.map(layerConfig => this
|
|
40
|
+
const layers = layerConfigs.map(layerConfig => this.#layers[layerConfig.id]);
|
|
38
41
|
|
|
39
42
|
const layer = layers[0];
|
|
40
43
|
if (layer.visibility === 'none') {
|
|
41
44
|
continue;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
sourceGroup = this.familiesBySource[sourceId] = {};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const sourceLayerId = layer.sourceLayer || '_geojsonTileLayer';
|
|
51
|
-
let sourceLayerFamilies = sourceGroup[sourceLayerId];
|
|
52
|
-
if (!sourceLayerFamilies) {
|
|
53
|
-
sourceLayerFamilies = sourceGroup[sourceLayerId] = [];
|
|
54
|
-
}
|
|
55
|
-
|
|
47
|
+
const { source = '', sourceLayer = '_geojsonTileLayer' } = layer;
|
|
48
|
+
const sourceGroup = (this.familiesBySource[source] ??= {});
|
|
49
|
+
const sourceLayerFamilies = (sourceGroup[sourceLayer] ??= []);
|
|
56
50
|
sourceLayerFamilies.push(layers);
|
|
57
51
|
}
|
|
58
52
|
}
|
|
@@ -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;
|
|
@@ -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);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const { toString, ValueType, BooleanType, CollatorType } = require('../types');
|
|
2
|
+
const Assertion = require('./assertion');
|
|
3
|
+
const { typeOf } = require('../values');
|
|
4
|
+
const RuntimeError = require('../runtime_error');
|
|
5
|
+
|
|
6
|
+
function isComparableType(op, type) {
|
|
7
|
+
if (op === '==' || op === '!=') {
|
|
8
|
+
// equality operator
|
|
9
|
+
return (
|
|
10
|
+
type.kind === 'boolean' ||
|
|
11
|
+
type.kind === 'string' ||
|
|
12
|
+
type.kind === 'number' ||
|
|
13
|
+
type.kind === 'null' ||
|
|
14
|
+
type.kind === 'value'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
// ordering operator
|
|
18
|
+
return type.kind === 'string' || type.kind === 'number' || type.kind === 'value';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function eq(ctx, a, b) {
|
|
22
|
+
return a === b;
|
|
23
|
+
}
|
|
24
|
+
function neq(ctx, a, b) {
|
|
25
|
+
return a !== b;
|
|
26
|
+
}
|
|
27
|
+
function lt(ctx, a, b) {
|
|
28
|
+
return a < b;
|
|
29
|
+
}
|
|
30
|
+
function gt(ctx, a, b) {
|
|
31
|
+
return a > b;
|
|
32
|
+
}
|
|
33
|
+
function lteq(ctx, a, b) {
|
|
34
|
+
return a <= b;
|
|
35
|
+
}
|
|
36
|
+
function gteq(ctx, a, b) {
|
|
37
|
+
return a >= b;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function eqCollate(ctx, a, b, c) {
|
|
41
|
+
return c.compare(a, b) === 0;
|
|
42
|
+
}
|
|
43
|
+
function neqCollate(ctx, a, b, c) {
|
|
44
|
+
return !eqCollate(ctx, a, b, c);
|
|
45
|
+
}
|
|
46
|
+
function ltCollate(ctx, a, b, c) {
|
|
47
|
+
return c.compare(a, b) < 0;
|
|
48
|
+
}
|
|
49
|
+
function gtCollate(ctx, a, b, c) {
|
|
50
|
+
return c.compare(a, b) > 0;
|
|
51
|
+
}
|
|
52
|
+
function lteqCollate(ctx, a, b, c) {
|
|
53
|
+
return c.compare(a, b) <= 0;
|
|
54
|
+
}
|
|
55
|
+
function gteqCollate(ctx, a, b, c) {
|
|
56
|
+
return c.compare(a, b) >= 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Special form for comparison operators, implementing the signatures:
|
|
61
|
+
* - (T, T, ?Collator) => boolean
|
|
62
|
+
* - (T, value, ?Collator) => boolean
|
|
63
|
+
* - (value, T, ?Collator) => boolean
|
|
64
|
+
*
|
|
65
|
+
* For inequalities, T must be either value, string, or number. For ==/!=, it
|
|
66
|
+
* can also be boolean or null.
|
|
67
|
+
*
|
|
68
|
+
* Equality semantics are equivalent to Javascript's strict equality (===/!==)
|
|
69
|
+
* -- i.e., when the arguments' types don't match, == evaluates to false, != to
|
|
70
|
+
* true.
|
|
71
|
+
*
|
|
72
|
+
* When types don't match in an ordering comparison, a runtime error is thrown.
|
|
73
|
+
*
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
function makeComparison(op, compareBasic, compareWithCollator) {
|
|
77
|
+
const isOrderComparison = op !== '==' && op !== '!=';
|
|
78
|
+
|
|
79
|
+
return class Comparison {
|
|
80
|
+
constructor(lhs, rhs, collator) {
|
|
81
|
+
this.type = BooleanType;
|
|
82
|
+
this.lhs = lhs;
|
|
83
|
+
this.rhs = rhs;
|
|
84
|
+
this.collator = collator;
|
|
85
|
+
this.hasUntypedArgument = lhs.type.kind === 'value' || rhs.type.kind === 'value';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static parse(args, context) {
|
|
89
|
+
if (args.length !== 3 && args.length !== 4) return context.error('Expected two or three arguments.');
|
|
90
|
+
|
|
91
|
+
const op = args[0];
|
|
92
|
+
|
|
93
|
+
let lhs = context.parse(args[1], 1, ValueType);
|
|
94
|
+
if (!lhs) return null;
|
|
95
|
+
if (!isComparableType(op, lhs.type)) {
|
|
96
|
+
return context.concat(1).error(`"${op}" comparisons are not supported for type '${toString(lhs.type)}'.`);
|
|
97
|
+
}
|
|
98
|
+
let rhs = context.parse(args[2], 2, ValueType);
|
|
99
|
+
if (!rhs) return null;
|
|
100
|
+
if (!isComparableType(op, rhs.type)) {
|
|
101
|
+
return context.concat(2).error(`"${op}" comparisons are not supported for type '${toString(rhs.type)}'.`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (lhs.type.kind !== rhs.type.kind && lhs.type.kind !== 'value' && rhs.type.kind !== 'value') {
|
|
105
|
+
return context.error(`Cannot compare types '${toString(lhs.type)}' and '${toString(rhs.type)}'.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isOrderComparison) {
|
|
109
|
+
// typing rules specific to less/greater than operators
|
|
110
|
+
if (lhs.type.kind === 'value' && rhs.type.kind !== 'value') {
|
|
111
|
+
// (value, T)
|
|
112
|
+
lhs = new Assertion(rhs.type, [lhs]);
|
|
113
|
+
} else if (lhs.type.kind !== 'value' && rhs.type.kind === 'value') {
|
|
114
|
+
// (T, value)
|
|
115
|
+
rhs = new Assertion(lhs.type, [rhs]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let collator = null;
|
|
120
|
+
if (args.length === 4) {
|
|
121
|
+
if (
|
|
122
|
+
lhs.type.kind !== 'string' &&
|
|
123
|
+
rhs.type.kind !== 'string' &&
|
|
124
|
+
lhs.type.kind !== 'value' &&
|
|
125
|
+
rhs.type.kind !== 'value'
|
|
126
|
+
) {
|
|
127
|
+
return context.error('Cannot use collator to compare non-string types.');
|
|
128
|
+
}
|
|
129
|
+
collator = context.parse(args[3], 3, CollatorType);
|
|
130
|
+
if (!collator) return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return new Comparison(lhs, rhs, collator);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
evaluate(ctx) {
|
|
137
|
+
const lhs = this.lhs.evaluate(ctx);
|
|
138
|
+
const rhs = this.rhs.evaluate(ctx);
|
|
139
|
+
|
|
140
|
+
if (isOrderComparison && this.hasUntypedArgument) {
|
|
141
|
+
const lt = typeOf(lhs);
|
|
142
|
+
const rt = typeOf(rhs);
|
|
143
|
+
// check that type is string or number, and equal
|
|
144
|
+
if (lt.kind !== rt.kind || !(lt.kind === 'string' || lt.kind === 'number')) {
|
|
145
|
+
throw new RuntimeError(
|
|
146
|
+
`Expected arguments for "${op}" to be (string, string) or (number, number), but found (${lt.kind}, ${rt.kind}) instead.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.collator && !isOrderComparison && this.hasUntypedArgument) {
|
|
152
|
+
const lt = typeOf(lhs);
|
|
153
|
+
const rt = typeOf(rhs);
|
|
154
|
+
if (lt.kind !== 'string' || rt.kind !== 'string') {
|
|
155
|
+
return compareBasic(ctx, lhs, rhs);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return this.collator
|
|
160
|
+
? compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx))
|
|
161
|
+
: compareBasic(ctx, lhs, rhs);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
eachChild(fn) {
|
|
165
|
+
fn(this.lhs);
|
|
166
|
+
fn(this.rhs);
|
|
167
|
+
if (this.collator) {
|
|
168
|
+
fn(this.collator);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
possibleOutputs() {
|
|
173
|
+
return [true, false];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
serialize() {
|
|
177
|
+
const serialized = [op];
|
|
178
|
+
this.eachChild(child => {
|
|
179
|
+
serialized.push(child.serialize());
|
|
180
|
+
});
|
|
181
|
+
return serialized;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
Equals: makeComparison('==', eq, eqCollate),
|
|
188
|
+
NotEquals: makeComparison('!=', neq, neqCollate),
|
|
189
|
+
LessThan: makeComparison('<', lt, ltCollate),
|
|
190
|
+
GreaterThan: makeComparison('>', gt, gtCollate),
|
|
191
|
+
LessThanOrEqual: makeComparison('<=', lteq, lteqCollate),
|
|
192
|
+
GreaterThanOrEqual: makeComparison('>=', gteq, gteqCollate)
|
|
193
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const { NumberType, ValueType, FormattedType, array, StringType } = require('../types');
|
|
2
|
+
|
|
3
|
+
class FormattedSection {
|
|
4
|
+
constructor(text, scale, fontStack) {
|
|
5
|
+
this.text = text;
|
|
6
|
+
this.scale = scale;
|
|
7
|
+
this.fontStack = fontStack;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class Formatted {
|
|
12
|
+
constructor(sections) {
|
|
13
|
+
this.sections = sections;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
toString() {
|
|
17
|
+
return this.sections.map(section => section.text).join('');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
serialize() {
|
|
21
|
+
const serialized = ['format'];
|
|
22
|
+
for (const section of this.sections) {
|
|
23
|
+
serialized.push(section.text);
|
|
24
|
+
const fontStack = section.fontStack ? ['literal', section.fontStack.split(',')] : null;
|
|
25
|
+
serialized.push({ 'text-font': fontStack, 'font-scale': section.scale });
|
|
26
|
+
}
|
|
27
|
+
return serialized;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class FormatExpression {
|
|
32
|
+
constructor(sections) {
|
|
33
|
+
this.type = FormattedType;
|
|
34
|
+
this.sections = sections;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static parse(args, context) {
|
|
38
|
+
if (args.length < 3) {
|
|
39
|
+
return context.error('Expected at least two arguments.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if ((args.length - 1) % 2 !== 0) {
|
|
43
|
+
return context.error('Expected an even number of arguments.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sections = [];
|
|
47
|
+
for (let i = 1; i < args.length - 1; i += 2) {
|
|
48
|
+
const text = context.parse(args[i], 1, ValueType);
|
|
49
|
+
if (!text) return null;
|
|
50
|
+
const kind = text.type.kind;
|
|
51
|
+
if (kind !== 'string' && kind !== 'value' && kind !== 'null')
|
|
52
|
+
return context.error("Formatted text type must be 'string', 'value', or 'null'.");
|
|
53
|
+
|
|
54
|
+
const options = args[i + 1];
|
|
55
|
+
if (typeof options !== 'object' || Array.isArray(options))
|
|
56
|
+
return context.error('Format options argument must be an object.');
|
|
57
|
+
|
|
58
|
+
let scale = null;
|
|
59
|
+
if (options['font-scale']) {
|
|
60
|
+
scale = context.parse(options['font-scale'], 1, NumberType);
|
|
61
|
+
if (!scale) return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let font = null;
|
|
65
|
+
if (options['text-font']) {
|
|
66
|
+
font = context.parse(options['text-font'], 1, array(StringType));
|
|
67
|
+
if (!font) return null;
|
|
68
|
+
}
|
|
69
|
+
sections.push({ text, scale, font });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return new FormatExpression(sections);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
evaluate(ctx) {
|
|
76
|
+
return new Formatted(
|
|
77
|
+
this.sections.map(
|
|
78
|
+
section =>
|
|
79
|
+
new FormattedSection(
|
|
80
|
+
section.text.evaluate(ctx) || '',
|
|
81
|
+
section.scale ? section.scale.evaluate(ctx) : null,
|
|
82
|
+
section.font ? section.font.evaluate(ctx).join(',') : null
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
eachChild(fn) {
|
|
89
|
+
for (const section of this.sections) {
|
|
90
|
+
fn(section.text);
|
|
91
|
+
if (section.scale) {
|
|
92
|
+
fn(section.scale);
|
|
93
|
+
}
|
|
94
|
+
if (section.font) {
|
|
95
|
+
fn(section.font);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
possibleOutputs() {
|
|
101
|
+
// Technically the combinatoric set of all children
|
|
102
|
+
// Usually, this.text will be undefined anyway
|
|
103
|
+
return [undefined];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
serialize() {
|
|
107
|
+
const serialized = ['format'];
|
|
108
|
+
for (const section of this.sections) {
|
|
109
|
+
serialized.push(section.text.serialize());
|
|
110
|
+
const options = {};
|
|
111
|
+
if (section.scale) {
|
|
112
|
+
options['font-scale'] = section.scale.serialize();
|
|
113
|
+
}
|
|
114
|
+
if (section.font) {
|
|
115
|
+
options['text-font'] = section.font.serialize();
|
|
116
|
+
}
|
|
117
|
+
serialized.push(options);
|
|
118
|
+
}
|
|
119
|
+
return serialized;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { Formatted, FormatExpression };
|
|
@@ -26,21 +26,29 @@ const Case = require('./case');
|
|
|
26
26
|
const Step = require('./step');
|
|
27
27
|
const Interpolate = require('./interpolate');
|
|
28
28
|
const Coalesce = require('./coalesce');
|
|
29
|
-
const { Equals, NotEquals } = require('./
|
|
29
|
+
const { Equals, NotEquals, LessThan, GreaterThan, LessThanOrEqual, GreaterThanOrEqual } = require('./comparison');
|
|
30
30
|
const { CollatorExpression } = require('./collator');
|
|
31
|
+
const { Formatted, FormatExpression } = require('./formatted');
|
|
31
32
|
const Length = require('./length');
|
|
32
33
|
|
|
33
34
|
const expressions = {
|
|
34
35
|
// special forms
|
|
35
36
|
'==': Equals,
|
|
36
37
|
'!=': NotEquals,
|
|
38
|
+
'>': GreaterThan,
|
|
39
|
+
'<': LessThan,
|
|
40
|
+
'>=': GreaterThanOrEqual,
|
|
41
|
+
'<=': LessThanOrEqual,
|
|
37
42
|
array: ArrayAssertion,
|
|
38
43
|
at: At,
|
|
39
44
|
boolean: Assertion,
|
|
40
45
|
case: Case,
|
|
41
46
|
coalesce: Coalesce,
|
|
42
47
|
collator: CollatorExpression,
|
|
48
|
+
format: FormatExpression,
|
|
43
49
|
interpolate: Interpolate,
|
|
50
|
+
'interpolate-hcl': Interpolate,
|
|
51
|
+
'interpolate-lab': Interpolate,
|
|
44
52
|
length: Length,
|
|
45
53
|
let: Let,
|
|
46
54
|
literal: Literal,
|
|
@@ -73,32 +81,6 @@ function get(key, obj) {
|
|
|
73
81
|
return typeof v === 'undefined' ? null : v;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
function lt(ctx, [a, b]) {
|
|
77
|
-
return a.evaluate(ctx) < b.evaluate(ctx);
|
|
78
|
-
}
|
|
79
|
-
function gt(ctx, [a, b]) {
|
|
80
|
-
return a.evaluate(ctx) > b.evaluate(ctx);
|
|
81
|
-
}
|
|
82
|
-
function lteq(ctx, [a, b]) {
|
|
83
|
-
return a.evaluate(ctx) <= b.evaluate(ctx);
|
|
84
|
-
}
|
|
85
|
-
function gteq(ctx, [a, b]) {
|
|
86
|
-
return a.evaluate(ctx) >= b.evaluate(ctx);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function ltCollate(ctx, [a, b, c]) {
|
|
90
|
-
return c.evaluate(ctx).compare(a.evaluate(ctx), b.evaluate(ctx)) < 0;
|
|
91
|
-
}
|
|
92
|
-
function gtCollate(ctx, [a, b, c]) {
|
|
93
|
-
return c.evaluate(ctx).compare(a.evaluate(ctx), b.evaluate(ctx)) > 0;
|
|
94
|
-
}
|
|
95
|
-
function lteqCollate(ctx, [a, b, c]) {
|
|
96
|
-
return c.evaluate(ctx).compare(a.evaluate(ctx), b.evaluate(ctx)) <= 0;
|
|
97
|
-
}
|
|
98
|
-
function gteqCollate(ctx, [a, b, c]) {
|
|
99
|
-
return c.evaluate(ctx).compare(a.evaluate(ctx), b.evaluate(ctx)) >= 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
84
|
function binarySearch(v, a, i, j) {
|
|
103
85
|
while (i <= j) {
|
|
104
86
|
const m = (i + j) >> 1;
|
|
@@ -134,7 +116,7 @@ CompoundExpression.register(expressions, {
|
|
|
134
116
|
if (type === 'string' || type === 'number' || type === 'boolean') {
|
|
135
117
|
return String(v);
|
|
136
118
|
}
|
|
137
|
-
if (v instanceof Color) {
|
|
119
|
+
if (v instanceof Color || v instanceof Formatted) {
|
|
138
120
|
return v.toString();
|
|
139
121
|
}
|
|
140
122
|
return JSON.stringify(v);
|
|
@@ -323,38 +305,6 @@ CompoundExpression.register(expressions, {
|
|
|
323
305
|
// assumes v is a array literal with values sorted in ascending order and of a single type
|
|
324
306
|
(ctx, [k, v]) => binarySearch(ctx.properties()[k.value], v.value, 0, v.value.length - 1)
|
|
325
307
|
],
|
|
326
|
-
'>': {
|
|
327
|
-
type: BooleanType,
|
|
328
|
-
overloads: [
|
|
329
|
-
[[NumberType, NumberType], gt],
|
|
330
|
-
[[StringType, StringType], gt],
|
|
331
|
-
[[StringType, StringType, CollatorType], gtCollate]
|
|
332
|
-
]
|
|
333
|
-
},
|
|
334
|
-
'<': {
|
|
335
|
-
type: BooleanType,
|
|
336
|
-
overloads: [
|
|
337
|
-
[[NumberType, NumberType], lt],
|
|
338
|
-
[[StringType, StringType], lt],
|
|
339
|
-
[[StringType, StringType, CollatorType], ltCollate]
|
|
340
|
-
]
|
|
341
|
-
},
|
|
342
|
-
'>=': {
|
|
343
|
-
type: BooleanType,
|
|
344
|
-
overloads: [
|
|
345
|
-
[[NumberType, NumberType], gteq],
|
|
346
|
-
[[StringType, StringType], gteq],
|
|
347
|
-
[[StringType, StringType, CollatorType], gteqCollate]
|
|
348
|
-
]
|
|
349
|
-
},
|
|
350
|
-
'<=': {
|
|
351
|
-
type: BooleanType,
|
|
352
|
-
overloads: [
|
|
353
|
-
[[NumberType, NumberType], lteq],
|
|
354
|
-
[[StringType, StringType], lteq],
|
|
355
|
-
[[StringType, StringType, CollatorType], lteqCollate]
|
|
356
|
-
]
|
|
357
|
-
},
|
|
358
308
|
all: {
|
|
359
309
|
type: BooleanType,
|
|
360
310
|
overloads: [
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const UnitBezier = require('@mapbox/unitbezier');
|
|
2
2
|
|
|
3
3
|
const interpolate = require('../../util/interpolate');
|
|
4
|
-
const { toString, NumberType } = require('../types');
|
|
4
|
+
const { toString, ColorType, NumberType } = require('../types');
|
|
5
5
|
const { findStopLessThanOrEqualTo } = require('../stops');
|
|
6
|
+
const { hcl, lab } = require('../../util/color_spaces');
|
|
6
7
|
|
|
7
8
|
class Interpolate {
|
|
8
|
-
constructor(type, interpolation, input, stops) {
|
|
9
|
+
constructor(type, operator, interpolation, input, stops) {
|
|
9
10
|
this.type = type;
|
|
11
|
+
this.operator = operator;
|
|
10
12
|
this.interpolation = interpolation;
|
|
11
13
|
this.input = input;
|
|
12
14
|
|
|
@@ -33,7 +35,7 @@ class Interpolate {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
static parse(args, context) {
|
|
36
|
-
let [, interpolation, input, ...rest] = args;
|
|
38
|
+
let [operator, interpolation, input, ...rest] = args;
|
|
37
39
|
|
|
38
40
|
if (!Array.isArray(interpolation) || interpolation.length === 0) {
|
|
39
41
|
return context.error('Expected an interpolation type expression.', 1);
|
|
@@ -79,7 +81,9 @@ class Interpolate {
|
|
|
79
81
|
const stops = [];
|
|
80
82
|
|
|
81
83
|
let outputType = null;
|
|
82
|
-
if (
|
|
84
|
+
if (operator === 'interpolate-hcl' || operator === 'interpolate-lab') {
|
|
85
|
+
outputType = ColorType;
|
|
86
|
+
} else if (context.expectedType && context.expectedType.kind !== 'value') {
|
|
83
87
|
outputType = context.expectedType;
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -118,7 +122,7 @@ class Interpolate {
|
|
|
118
122
|
return context.error(`Type ${toString(outputType)} is not interpolatable.`);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
return new Interpolate(outputType, interpolation, input, stops);
|
|
125
|
+
return new Interpolate(outputType, operator, interpolation, input, stops);
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
evaluate(ctx) {
|
|
@@ -147,7 +151,13 @@ class Interpolate {
|
|
|
147
151
|
const outputLower = outputs[index].evaluate(ctx);
|
|
148
152
|
const outputUpper = outputs[index + 1].evaluate(ctx);
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
if (this.operator === 'interpolate') {
|
|
155
|
+
return interpolate[this.type.kind.toLowerCase()](outputLower, outputUpper, t);
|
|
156
|
+
}
|
|
157
|
+
if (this.operator === 'interpolate-hcl') {
|
|
158
|
+
return hcl.reverse(hcl.interpolate(hcl.forward(outputLower), hcl.forward(outputUpper), t));
|
|
159
|
+
}
|
|
160
|
+
return lab.reverse(lab.interpolate(lab.forward(outputLower), lab.forward(outputUpper), t));
|
|
151
161
|
}
|
|
152
162
|
|
|
153
163
|
eachChild(fn) {
|
|
@@ -175,7 +185,7 @@ class Interpolate {
|
|
|
175
185
|
interpolation = ['cubic-bezier'].concat(this.interpolation.controlPoints);
|
|
176
186
|
}
|
|
177
187
|
|
|
178
|
-
const serialized = [
|
|
188
|
+
const serialized = [this.operator, interpolation, this.input.serialize()];
|
|
179
189
|
|
|
180
190
|
for (let i = 0; i < this.labels.length; i++) {
|
|
181
191
|
serialized.push(this.labels[i], this.outputs[i].serialize());
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { isValue, typeOf, Color } = require('../values');
|
|
3
|
+
const { Formatted } = require('./formatted');
|
|
3
4
|
|
|
4
5
|
class Literal {
|
|
5
6
|
constructor(type, value) {
|
|
@@ -51,6 +52,10 @@ class Literal {
|
|
|
51
52
|
// so we have to implement an equivalent serialization here
|
|
52
53
|
return ['rgba'].concat(this.value.toArray());
|
|
53
54
|
}
|
|
55
|
+
if (this.value instanceof Formatted) {
|
|
56
|
+
// Same as Color
|
|
57
|
+
return this.value.serialize();
|
|
58
|
+
}
|
|
54
59
|
assert(
|
|
55
60
|
this.value === null ||
|
|
56
61
|
typeof this.value === 'string' ||
|
|
@@ -97,6 +97,10 @@ class ParsingContext {
|
|
|
97
97
|
if (!options.omitTypeAnnotations) {
|
|
98
98
|
parsed = new Coercion(expected, [parsed]);
|
|
99
99
|
}
|
|
100
|
+
} else if (expected.kind === 'formatted' && (actual.kind === 'value' || actual.kind === 'string')) {
|
|
101
|
+
if (!options.omitTypeAnnotations) {
|
|
102
|
+
parsed = new Coercion(expected, [parsed]);
|
|
103
|
+
}
|
|
100
104
|
} else if (this.checkSubtype(this.expectedType, parsed.type)) {
|
|
101
105
|
return null;
|
|
102
106
|
}
|