@mapbox/mapbox-gl-style-spec 14.8.0 → 14.9.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.
@@ -138,7 +138,7 @@ class Coercion implements Expression {
138
138
  // created by properties that expect the 'formatted' type.
139
139
  return Formatted.fromString(valueToString(this.args[0].evaluate(ctx)));
140
140
  } else if (this.type.kind === 'resolvedImage') {
141
- return ResolvedImage.fromString(valueToString(this.args[0].evaluate(ctx)));
141
+ return ResolvedImage.build(valueToString(this.args[0].evaluate(ctx)));
142
142
  } else if (this.type.kind === 'array') {
143
143
  return this.args.map(arg => { return arg.evaluate(ctx); });
144
144
  } else {
@@ -19,7 +19,7 @@ function coerceValue(type: string, value: any): any {
19
19
  return Formatted.fromString(valueToString(value));
20
20
  }
21
21
  case 'resolvedImage': {
22
- return ResolvedImage.fromString(valueToString(value));
22
+ return ResolvedImage.build(valueToString(value));
23
23
  }
24
24
  }
25
25
  return value;
@@ -1,20 +1,47 @@
1
- import {ResolvedImageType, StringType} from '../types';
1
+ import {ColorType, ResolvedImageType, StringType} from '../types';
2
2
  import ResolvedImage from '../types/resolved_image';
3
+ import {isExpression} from '..';
3
4
 
5
+ import type Color from '../../util/color';
4
6
  import type {Expression, SerializedExpression} from '../expression';
5
7
  import type EvaluationContext from '../evaluation_context';
6
8
  import type ParsingContext from '../parsing_context';
7
9
  import type {Type} from '../types';
8
10
 
11
+ export type ImageParams = Record<string, Expression>;
12
+
13
+ export type ImageOptions = {
14
+ params: ImageParams;
15
+ }
16
+
17
+ function isImageOptions(value: unknown) {
18
+ if (typeof value !== 'string' && !isExpression(value)) {
19
+ return true;
20
+ }
21
+
22
+ return false;
23
+ }
24
+
9
25
  export default class ImageExpression implements Expression {
10
26
  type: Type;
11
27
  inputPrimary: Expression;
28
+ inputPrimaryParams: Record<string, Expression> | undefined;
12
29
  inputSecondary: Expression | null | undefined;
30
+ inputSecondaryParams: Record<string, Expression> | undefined;
13
31
 
14
- constructor(inputPrimary: Expression, inputSecondary?: Expression | null) {
32
+ _imageWarnHistory: Record<string, boolean> = {};
33
+
34
+ constructor(
35
+ inputPrimary: Expression,
36
+ inputSecondary?: Expression | null,
37
+ inputPrimaryParams?: Record<string, Expression>,
38
+ inputSecondaryParams?: Record<string, Expression>
39
+ ) {
15
40
  this.type = ResolvedImageType;
16
41
  this.inputPrimary = inputPrimary;
17
42
  this.inputSecondary = inputSecondary;
43
+ this.inputPrimaryParams = inputPrimaryParams;
44
+ this.inputSecondaryParams = inputSecondaryParams;
18
45
  }
19
46
 
20
47
  static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Expression | null | undefined {
@@ -23,23 +50,126 @@ export default class ImageExpression implements Expression {
23
50
  return context.error(`Expected two or more arguments.`);
24
51
  }
25
52
 
26
- const namePrimary = context.parse(args[1], 1, StringType);
27
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Expression'.
28
- if (!namePrimary) return context.error(`No image name provided.`);
53
+ let nextArgId = 1;
54
+ const imageExpression: Array<{image: Expression, options: Record<string, Expression>}> = [];
55
+
56
+ function tryParseImage() {
57
+ if (nextArgId < args.length) {
58
+ const imageName = context.parse(args[nextArgId], nextArgId++, StringType);
59
+ if (!imageName) {
60
+ context.error(imageExpression.length ? `Secondary image variant is not a string.` : `No image name provided.`);
61
+ return false;
62
+ }
63
+
64
+ imageExpression.push({image: imageName, options: undefined});
65
+ return true;
66
+ }
67
+
68
+ return true;
69
+ }
70
+
71
+ function tryParseOptions() {
72
+ if (nextArgId < args.length) {
73
+ if (!isImageOptions(args[nextArgId])) {
74
+ return true;
75
+ }
76
+
77
+ const params = (args[nextArgId] as ImageOptions).params;
78
+
79
+ const optionsContext = context.concat(nextArgId);
80
+
81
+ if (!params) {
82
+ nextArgId++;
83
+ return true;
84
+ }
85
+
86
+ if (typeof params !== 'object' || params.constructor !== Object) {
87
+ optionsContext.error(`Image options \"params\" should be an object`);
88
+ return false;
89
+ }
90
+
91
+ const parsed = {};
92
+
93
+ const childContext = optionsContext.concat(undefined, 'params');
94
+
95
+ for (const key in params) {
96
+ if (!key) {
97
+ childContext.error(`Image parameter name should be non-empty`);
98
+ return false;
99
+ }
100
+
101
+ const value = childContext.concat(undefined, key).parse(params[key], undefined, ColorType, undefined, {typeAnnotation: 'coerce'});
102
+ if (!value) {
103
+ return false;
104
+ }
105
+
106
+ parsed[key] = value;
107
+ }
108
+
109
+ imageExpression[imageExpression.length - 1].options = parsed;
110
+ nextArgId++;
111
+
112
+ return true;
113
+ }
114
+
115
+ return true;
116
+ }
117
+
118
+ // Parse the primary and secondary image expressions
119
+ for (let i = 0; i < 2; i++) {
120
+ if (!tryParseImage() || !tryParseOptions()) {
121
+ return;
122
+ }
123
+ }
29
124
 
30
- if (args.length === 2) {
31
- return new ImageExpression(namePrimary);
125
+ return new ImageExpression(
126
+ imageExpression[0].image,
127
+ imageExpression[1] ? imageExpression[1].image : undefined,
128
+ imageExpression[0].options,
129
+ imageExpression[1] ? imageExpression[1].options : undefined
130
+ );
131
+ }
132
+
133
+ evaluateParams(ctx: EvaluationContext, params: Record<string, Expression> | undefined): {params: Record<string, Color>} {
134
+ const result: Record<string, Color> = {};
135
+ if (params) {
136
+ for (const key in params) {
137
+ if (params[key]) {
138
+ try {
139
+ const color = params[key].evaluate(ctx);
140
+ const msg = `Ignoring image parameter "${key}" with semi-transparent color ${color.toString()}`;
141
+
142
+ if (color.a !== 1) {
143
+ if (!this._imageWarnHistory[msg]) {
144
+ console.warn(msg);
145
+ this._imageWarnHistory[msg] = true;
146
+ }
147
+ continue;
148
+ }
149
+ result[key] = color;
150
+ } catch (err) {
151
+ continue;
152
+ }
153
+ }
154
+ }
155
+ } else {
156
+ return undefined;
32
157
  }
33
158
 
34
- const nameSecondary = context.parse(args[2], 1, StringType);
35
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Expression'.
36
- if (!nameSecondary) return context.error(`Secondary image variant is not a string.`);
159
+ if (Object.keys(result).length === 0) {
160
+ return undefined;
161
+ }
37
162
 
38
- return new ImageExpression(namePrimary, nameSecondary);
163
+ return {params: result};
39
164
  }
40
165
 
41
166
  evaluate(ctx: EvaluationContext): null | ResolvedImage {
42
- const value = ResolvedImage.fromString(this.inputPrimary.evaluate(ctx), this.inputSecondary ? this.inputSecondary.evaluate(ctx) : undefined);
167
+ const value = ResolvedImage.build(
168
+ this.inputPrimary.evaluate(ctx),
169
+ this.inputSecondary ? this.inputSecondary.evaluate(ctx) : undefined,
170
+ this.inputPrimaryParams ? this.evaluateParams(ctx, this.inputPrimaryParams) : undefined,
171
+ this.inputSecondaryParams ? this.evaluateParams(ctx, this.inputSecondaryParams) : undefined
172
+ );
43
173
  if (value && ctx.availableImages) {
44
174
  value.available = ctx.availableImages.indexOf(value.namePrimary) > -1;
45
175
  // If there's a secondary variant, only mark it available if both are present
@@ -53,8 +183,22 @@ export default class ImageExpression implements Expression {
53
183
 
54
184
  eachChild(fn: (_: Expression) => void) {
55
185
  fn(this.inputPrimary);
186
+ if (this.inputPrimaryParams) {
187
+ for (const key in this.inputPrimaryParams) {
188
+ if (this.inputPrimaryParams[key]) {
189
+ fn(this.inputPrimaryParams[key]);
190
+ }
191
+ }
192
+ }
56
193
  if (this.inputSecondary) {
57
194
  fn(this.inputSecondary);
195
+ if (this.inputSecondaryParams) {
196
+ for (const key in this.inputSecondaryParams) {
197
+ if (this.inputSecondaryParams[key]) {
198
+ fn(this.inputSecondaryParams[key]);
199
+ }
200
+ }
201
+ }
58
202
  }
59
203
  }
60
204
 
@@ -63,10 +207,36 @@ export default class ImageExpression implements Expression {
63
207
  return false;
64
208
  }
65
209
 
210
+ serializeParams(params: Record<string, Expression> | undefined): {params: Record<string, SerializedExpression>} {
211
+ const result: Record<string, SerializedExpression> = {};
212
+ if (params) {
213
+ for (const key in params) {
214
+ if (params[key]) {
215
+ result[key] = params[key].serialize();
216
+ }
217
+ }
218
+ } else {
219
+ return undefined;
220
+ }
221
+
222
+ return {params: result};
223
+ }
224
+
66
225
  serialize(): SerializedExpression {
226
+ const serialized: SerializedExpression = ["image", this.inputPrimary.serialize()];
227
+
228
+ if (this.inputPrimaryParams) {
229
+ serialized.push(this.serializeParams(this.inputPrimaryParams));
230
+ }
231
+
67
232
  if (this.inputSecondary) {
68
- return ["image", this.inputPrimary.serialize(), this.inputSecondary.serialize()];
233
+ serialized.push(this.inputSecondary.serialize());
234
+
235
+ if (this.inputSecondaryParams) {
236
+ serialized.push(this.serializeParams(this.inputSecondaryParams));
237
+ }
69
238
  }
70
- return ["image", this.inputPrimary.serialize()];
239
+
240
+ return serialized;
71
241
  }
72
242
  }
@@ -182,6 +182,13 @@ CompoundExpression.register(expressions, {
182
182
  return v.evaluate(ctx).toRenderColor(null).toArray();
183
183
  }
184
184
  ],
185
+ 'to-hsla': [
186
+ array(NumberType, 4),
187
+ [ColorType],
188
+ (ctx, [v]) => {
189
+ return v.evaluate(ctx).toRenderColor(null).toHslaArray();
190
+ }
191
+ ],
185
192
  'rgb': [
186
193
  ColorType,
187
194
  [NumberType, NumberType, NumberType],
@@ -1,8 +1,10 @@
1
1
  import UnitBezier from '@mapbox/unitbezier';
2
2
  import * as interpolate from '../../util/interpolate';
3
- import {toString, NumberType, ColorType} from '../types';
3
+ import {toString, NumberType, ColorType, ValueType} from '../types';
4
4
  import {findStopLessThanOrEqualTo} from '../stops';
5
5
  import {hcl, lab} from '../../util/color_spaces';
6
+ import Literal from './literal';
7
+ import RuntimeError from '../runtime_error';
6
8
 
7
9
  import type Color from '../../util/color';
8
10
  import type {Stops} from '../stops';
@@ -27,14 +29,16 @@ class Interpolate implements Expression {
27
29
  operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab';
28
30
  interpolation: InterpolationType;
29
31
  input: Expression;
32
+ dynamicStops: Expression | null;
30
33
  labels: Array<number>;
31
34
  outputs: Array<Expression>;
32
35
 
33
- constructor(type: Type, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, stops: Stops) {
36
+ constructor(type: Type, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, dynamicStops: Expression | null, stops: Stops) {
34
37
  this.type = type;
35
38
  this.operator = operator;
36
39
  this.interpolation = interpolation;
37
40
  this.input = input;
41
+ this.dynamicStops = dynamicStops;
38
42
 
39
43
  this.labels = [];
40
44
  this.outputs = [];
@@ -101,12 +105,12 @@ class Interpolate implements Expression {
101
105
  return context.error(`Unknown interpolation type ${String(interpolation[0])}`, 1, 0);
102
106
  }
103
107
 
104
- if (args.length - 1 < 4) {
108
+ if (args.length - 1 < 3) {
105
109
  // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
106
- return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
110
+ return context.error(`Expected at least 3 arguments, but found only ${args.length - 1}.`);
107
111
  }
108
112
 
109
- if ((args.length - 1) % 2 !== 0) {
113
+ if (args.length - 1 > 3 && (args.length - 1) % 2 !== 0) {
110
114
  // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
111
115
  return context.error(`Expected an even number of arguments.`);
112
116
  }
@@ -123,6 +127,15 @@ class Interpolate implements Expression {
123
127
  outputType = context.expectedType;
124
128
  }
125
129
 
130
+ // Exactly 3 arguments means that the steps are created by an expression
131
+ if (args.length - 1 === 3) {
132
+ const dynamicStops = context.parse(rest[0], 3, ValueType);
133
+ if (!dynamicStops) return null;
134
+
135
+ // @ts-expect-error - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'InterpolationType'.
136
+ return new Interpolate(outputType, (operator as any), interpolation, input, dynamicStops, stops);
137
+ }
138
+
126
139
  for (let i = 0; i < rest.length; i += 2) {
127
140
  const label = rest[i];
128
141
  const value = rest[i + 1];
@@ -159,12 +172,36 @@ class Interpolate implements Expression {
159
172
  }
160
173
 
161
174
  // @ts-expect-error - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'InterpolationType'.
162
- return new Interpolate(outputType, (operator as any), interpolation, input, stops);
175
+ return new Interpolate(outputType, (operator as any), interpolation, input, null, stops);
163
176
  }
164
177
 
165
178
  evaluate(ctx: EvaluationContext): Color {
166
- const labels = this.labels;
167
- const outputs = this.outputs;
179
+ let labels = this.labels;
180
+ let outputs = this.outputs;
181
+
182
+ if (this.dynamicStops) {
183
+ const dynamicStopsValue = (this.dynamicStops.evaluate(ctx) as [number]);
184
+ if (dynamicStopsValue.length % 2 !== 0) {
185
+ throw new RuntimeError('Expected an even number of arguments.');
186
+ }
187
+ labels = [];
188
+ outputs = [];
189
+ for (let i = 0; i < dynamicStopsValue.length; i += 2) {
190
+ const label = dynamicStopsValue[i];
191
+ const output = new Literal(NumberType, dynamicStopsValue[i + 1]);
192
+ if (typeof label !== 'number') {
193
+ throw new RuntimeError('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.');
194
+ }
195
+ if (labels.length && labels[labels.length - 1] >= label) {
196
+ throw new RuntimeError('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.');
197
+ }
198
+ labels.push(label);
199
+ outputs.push(output);
200
+ }
201
+ if (labels.length === 0) {
202
+ throw new RuntimeError('Expected at least one input/output pair.');
203
+ }
204
+ }
168
205
 
169
206
  if (labels.length === 1) {
170
207
  return outputs[0].evaluate(ctx);
@@ -225,11 +262,15 @@ class Interpolate implements Expression {
225
262
 
226
263
  const serialized = [this.operator, interpolation, this.input.serialize()];
227
264
 
228
- for (let i = 0; i < this.labels.length; i++) {
229
- serialized.push(
230
- this.labels[i],
231
- this.outputs[i].serialize()
232
- );
265
+ if (this.dynamicStops) {
266
+ serialized.push(this.dynamicStops.serialize());
267
+ } else {
268
+ for (let i = 0; i < this.labels.length; i++) {
269
+ serialized.push(
270
+ this.labels[i],
271
+ this.outputs[i].serialize()
272
+ );
273
+ }
233
274
  }
234
275
  return serialized;
235
276
  }
@@ -22,6 +22,7 @@ export type ExpressionParser = (args: ReadonlyArray<unknown>, context: ParsingCo
22
22
  export type ExpressionRegistration = {
23
23
  new(...args: any[]): Expression;
24
24
  readonly parse: ExpressionParser;
25
+ _classRegistryKey?: string;
25
26
  };
26
27
 
27
28
  export type ExpressionRegistry = {
@@ -186,11 +186,13 @@ export class ZoomConstantExpression<Kind extends EvaluationKind> {
186
186
  configDependencies: Set<string>;
187
187
  _styleExpression: StyleExpression;
188
188
  isLightConstant: boolean | null | undefined;
189
+ isLineProgressConstant: boolean | null | undefined;
189
190
 
190
- constructor(kind: Kind, expression: StyleExpression, isLightConstant?: boolean | null) {
191
+ constructor(kind: Kind, expression: StyleExpression, isLightConstant?: boolean | null, isLineProgressConstant?: boolean | null) {
191
192
  this.kind = kind;
192
193
  this._styleExpression = expression;
193
194
  this.isLightConstant = isLightConstant;
195
+ this.isLineProgressConstant = isLineProgressConstant;
194
196
  this.isStateDependent = kind !== ('constant' as EvaluationKind) && !isConstant.isStateConstant(expression.expression);
195
197
  this.configDependencies = isConstant.getConfigDependencies(expression.expression);
196
198
  }
@@ -223,17 +225,19 @@ export class ZoomDependentExpression<Kind extends EvaluationKind> {
223
225
  zoomStops: Array<number>;
224
226
  isStateDependent: boolean;
225
227
  isLightConstant: boolean | null | undefined;
228
+ isLineProgressConstant: boolean | null | undefined;
226
229
  configDependencies: Set<string>;
227
230
 
228
231
  _styleExpression: StyleExpression;
229
232
  interpolationType: InterpolationType | null | undefined;
230
233
 
231
- constructor(kind: Kind, expression: StyleExpression, zoomStops: Array<number>, interpolationType?: InterpolationType, isLightConstant?: boolean | null) {
234
+ constructor(kind: Kind, expression: StyleExpression, zoomStops: Array<number>, interpolationType?: InterpolationType, isLightConstant?: boolean | null, isLineProgressConstant?: boolean | null) {
232
235
  this.kind = kind;
233
236
  this.zoomStops = zoomStops;
234
237
  this._styleExpression = expression;
235
238
  this.isStateDependent = kind !== ('camera' as EvaluationKind) && !isConstant.isStateConstant(expression.expression);
236
239
  this.isLightConstant = isLightConstant;
240
+ this.isLineProgressConstant = isLineProgressConstant;
237
241
  this.configDependencies = isConstant.getConfigDependencies(expression.expression);
238
242
  this.interpolationType = interpolationType;
239
243
  }
@@ -285,6 +289,7 @@ export type SourceExpression = {
285
289
  kind: 'source';
286
290
  isStateDependent: boolean;
287
291
  isLightConstant: boolean | null | undefined;
292
+ isLineProgressConstant: boolean | null | undefined;
288
293
  configDependencies: Set<string>;
289
294
  readonly evaluate: (
290
295
  globals: GlobalProperties,
@@ -316,6 +321,7 @@ export interface CompositeExpression {
316
321
  kind: 'composite';
317
322
  isStateDependent: boolean;
318
323
  isLightConstant: boolean | null | undefined;
324
+ isLineProgressConstant: boolean | null | undefined;
319
325
  configDependencies: Set<string>;
320
326
  readonly evaluate: (
321
327
  globals: GlobalProperties,
@@ -381,18 +387,18 @@ export function createPropertyExpression(
381
387
  if (!zoomCurve) {
382
388
  return success((isFeatureConstant && isLineProgressConstant) ?
383
389
  // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'unknown'.
384
- (new ZoomConstantExpression('constant', expression.value, isLightConstant) as ConstantExpression) :
390
+ (new ZoomConstantExpression('constant', expression.value, isLightConstant, isLineProgressConstant) as ConstantExpression) :
385
391
  // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'unknown'.
386
- (new ZoomConstantExpression('source', expression.value, isLightConstant) as SourceExpression));
392
+ (new ZoomConstantExpression('source', expression.value, isLightConstant, isLineProgressConstant) as SourceExpression));
387
393
  }
388
394
 
389
395
  const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined;
390
396
 
391
397
  return success((isFeatureConstant && isLineProgressConstant) ?
392
398
  // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'unknown'.
393
- (new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType, isLightConstant) as CameraExpression) :
399
+ (new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType, isLightConstant, isLineProgressConstant) as CameraExpression) :
394
400
  // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'unknown'.
395
- (new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType, isLightConstant) as CompositeExpression));
401
+ (new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType, isLightConstant, isLineProgressConstant) as CompositeExpression));
396
402
  }
397
403
 
398
404
  // serialization wrapper for old-style stop functions normalized to the
@@ -32,7 +32,7 @@ export default class Formatted {
32
32
  isEmpty(): boolean {
33
33
  if (this.sections.length === 0) return true;
34
34
  return !this.sections.some(section => section.text.length !== 0 ||
35
- (section.image && section.image.namePrimary.length !== 0));
35
+ (section.image && section.image.namePrimary));
36
36
  }
37
37
 
38
38
  static factory(text: Formatted | string): Formatted {
@@ -0,0 +1,54 @@
1
+ import type {RasterizationOptions} from "./resolved_image";
2
+
3
+ export class ImageIdWithOptions {
4
+ id: string;
5
+ options: RasterizationOptions;
6
+
7
+ constructor(id: string, options?: RasterizationOptions) {
8
+ this.id = id;
9
+ this.options = options || {params: {}};
10
+
11
+ if (!this.options.transform) {
12
+ this.options.transform = new DOMMatrix([1, 0, 0, 1, 0, 0]);
13
+ } else {
14
+ const {a, b, c, d, e, f} = this.options.transform;
15
+ this.options.transform = new DOMMatrix([a, b, c, d, e, f]);
16
+ }
17
+ }
18
+
19
+ static deserializeFromString(serialized: string): ImageIdWithOptions {
20
+ const deserializedObject = JSON.parse(serialized);
21
+ const options: RasterizationOptions = {params: deserializedObject.options.params};
22
+
23
+ const {a, b, c, d, e, f} = deserializedObject.options.transform;
24
+
25
+ options.transform = new DOMMatrix([a, b, c, d, e, f]);
26
+
27
+ return new ImageIdWithOptions(deserializedObject.id, deserializedObject.options);
28
+ }
29
+
30
+ scaleSelf(factor: number): this {
31
+ this.options.transform = this.options.transform.scale(factor);
32
+ return this;
33
+ }
34
+
35
+ serialize(): string {
36
+ const serialisedObject: Record<string, any> = {
37
+ id: this.id,
38
+ };
39
+
40
+ if (this.options) {
41
+ serialisedObject.options = this.options;
42
+ }
43
+
44
+ const {
45
+ a, b, c, d, e, f,
46
+ } = this.options.transform;
47
+
48
+ serialisedObject.options.transform = {
49
+ a, b, c, d, e, f,
50
+ };
51
+
52
+ return JSON.stringify(serialisedObject);
53
+ }
54
+ }
@@ -1,12 +1,25 @@
1
+ import {ImageIdWithOptions} from "./image_id_with_options";
2
+
3
+ import type Color from "../../util/color";
4
+
5
+ export type RasterizationOptions = {
6
+ params: Record<string, Color>;
7
+ transform?: DOMMatrix;
8
+ }
9
+
1
10
  export type ResolvedImageOptions = {
2
11
  namePrimary: string;
12
+ optionsPrimary: RasterizationOptions | null | undefined;
3
13
  nameSecondary: string | null | undefined;
14
+ optionsSecondary: RasterizationOptions | null | undefined;
4
15
  available: boolean;
5
16
  };
6
17
 
7
18
  export default class ResolvedImage {
8
19
  namePrimary: string;
20
+ optionsPrimary: RasterizationOptions | null | undefined;
9
21
  nameSecondary: string | null | undefined;
22
+ optionsSecondary: RasterizationOptions | null | undefined;
10
23
  available: boolean;
11
24
 
12
25
  constructor(options: ResolvedImageOptions) {
@@ -14,25 +27,54 @@ export default class ResolvedImage {
14
27
  if (options.nameSecondary) {
15
28
  this.nameSecondary = options.nameSecondary;
16
29
  }
30
+ if (options.optionsPrimary) {
31
+ this.optionsPrimary = options.optionsPrimary;
32
+ }
33
+ if (options.optionsSecondary) {
34
+ this.optionsSecondary = options.optionsSecondary;
35
+ }
17
36
  this.available = options.available;
18
37
  }
19
38
 
20
39
  toString(): string {
21
- if (this.nameSecondary) {
40
+ if (this.namePrimary && this.nameSecondary) {
22
41
  return `[${this.namePrimary},${this.nameSecondary}]`;
23
42
  }
43
+
24
44
  return this.namePrimary;
25
45
  }
26
46
 
27
- static fromString(namePrimary: string, nameSecondary?: string | null): ResolvedImage | null {
28
- if (!namePrimary) return null; // treat empty values as no image
29
- return new ResolvedImage({namePrimary, nameSecondary, available: false});
47
+ getPrimary(): ImageIdWithOptions {
48
+ return new ImageIdWithOptions(this.namePrimary, {
49
+ params: this.optionsPrimary ? (this.optionsPrimary.params || {}) : {},
50
+ });
30
51
  }
31
52
 
32
- serialize(): Array<string> {
53
+ getSerializedPrimary(): string {
54
+ return this.getPrimary().serialize();
55
+ }
56
+
57
+ getSecondary(): ImageIdWithOptions | null {
33
58
  if (this.nameSecondary) {
34
- return ["image", this.namePrimary, this.nameSecondary];
59
+ return new ImageIdWithOptions(this.nameSecondary, {
60
+ params: this.optionsSecondary ? (this.optionsSecondary.params || {}) : {},
61
+ });
35
62
  }
36
- return ["image", this.namePrimary];
63
+
64
+ return null;
65
+ }
66
+
67
+ static from(image: string | ResolvedImage): ResolvedImage {
68
+ return typeof image === 'string' ? ResolvedImage.build(image) : image;
69
+ }
70
+
71
+ static build(
72
+ namePrimary: string,
73
+ nameSecondary?: string | null,
74
+ optionsPrimary?: RasterizationOptions | null,
75
+ optionsSecondary?: RasterizationOptions | null
76
+ ): ResolvedImage | null {
77
+ if (!namePrimary) return null; // treat empty values as no image
78
+ return new ResolvedImage({namePrimary, nameSecondary, optionsPrimary, optionsSecondary, available: false});
37
79
  }
38
80
  }