@versatiles/style 4.2.8 → 4.3.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.
@@ -1,3 +1,4 @@
1
1
  import type { MaplibreLayer } from '../types/index.js';
2
2
  import type { StyleRules } from './types.js';
3
- export declare function decorate(layers: MaplibreLayer[], rules: StyleRules): MaplibreLayer[];
3
+ import type { CachedRecolor } from './recolor.ts';
4
+ export declare function decorate(layers: MaplibreLayer[], rules: StyleRules, recolor: CachedRecolor): MaplibreLayer[];
@@ -2,7 +2,7 @@ import Color from 'color';
2
2
  import expandBraces from 'brace-expansion';
3
3
  import maplibreProperties from '../shortbread/properties.js';
4
4
  import { deepMerge } from '../lib/utils.js';
5
- export function decorate(layers, rules) {
5
+ export function decorate(layers, rules, recolor) {
6
6
  const layerIds = layers.map(l => l.id);
7
7
  const layerIdSet = new Set(layerIds);
8
8
  // Initialize a new map to hold final styles for layers
@@ -39,89 +39,90 @@ export function decorate(layers, rules) {
39
39
  processStyling(layer, layerStyle);
40
40
  return [layer];
41
41
  });
42
- }
43
- // Function to process each style attribute for the layer
44
- function processStyling(layer, styleRule) {
45
- for (const [ruleKeyCamelCase, ruleValue] of Object.entries(styleRule)) {
46
- if (ruleValue == null)
47
- continue;
48
- // CamelCase to not-camel-case
49
- const ruleKey = ruleKeyCamelCase.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
50
- const propertyDefs = maplibreProperties.get(layer.type + '/' + ruleKey);
51
- if (!propertyDefs)
52
- continue;
53
- propertyDefs.forEach(propertyDef => {
54
- const { key } = propertyDef;
55
- let value = ruleValue;
56
- switch (propertyDef.valueType) {
57
- case 'color':
58
- value = processExpression(value, processColor);
59
- break;
60
- case 'fonts':
61
- value = processExpression(value, processFont);
62
- break;
63
- case 'resolvedImage':
64
- case 'formatted':
65
- case 'array':
66
- case 'boolean':
67
- case 'enum':
68
- case 'number':
69
- value = processExpression(value);
70
- break;
71
- default: throw new Error(`unknown propertyDef.valueType "${propertyDef.valueType}" for key "${key}"`);
42
+ // Function to process each style attribute for the layer
43
+ function processStyling(layer, styleRule) {
44
+ for (const [ruleKeyCamelCase, ruleValue] of Object.entries(styleRule)) {
45
+ if (ruleValue == null)
46
+ continue;
47
+ // CamelCase to not-camel-case
48
+ const ruleKey = ruleKeyCamelCase.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
49
+ const propertyDefs = maplibreProperties.get(layer.type + '/' + ruleKey);
50
+ if (!propertyDefs)
51
+ continue;
52
+ propertyDefs.forEach(propertyDef => {
53
+ const { key } = propertyDef;
54
+ let value = ruleValue;
55
+ switch (propertyDef.valueType) {
56
+ case 'color':
57
+ value = processExpression(value, processColor);
58
+ break;
59
+ case 'fonts':
60
+ value = processExpression(value, processFont);
61
+ break;
62
+ case 'resolvedImage':
63
+ case 'formatted':
64
+ case 'array':
65
+ case 'boolean':
66
+ case 'enum':
67
+ case 'number':
68
+ value = processExpression(value);
69
+ break;
70
+ default: throw new Error(`unknown propertyDef.valueType "${propertyDef.valueType}" for key "${key}"`);
71
+ }
72
+ switch (propertyDef.parent) {
73
+ case 'layer':
74
+ // @ts-expect-error: too complex to handle
75
+ layer[key] = value;
76
+ break;
77
+ case 'layout':
78
+ if (!layer.layout)
79
+ layer.layout = {};
80
+ // @ts-expect-error: too complex to handle
81
+ layer.layout[key] = value;
82
+ break;
83
+ case 'paint':
84
+ if (!layer.paint)
85
+ layer.paint = {};
86
+ // @ts-expect-error: too complex to handle
87
+ layer.paint[key] = value;
88
+ break;
89
+ default:
90
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
91
+ throw new Error(`unknown parent "${propertyDef.parent}" for key "${key}"`);
92
+ }
93
+ });
94
+ }
95
+ function processColor(value) {
96
+ if (typeof value === 'string')
97
+ value = Color(value);
98
+ if (value instanceof Color) {
99
+ const color = recolor.do(value);
100
+ const text = (color.alpha() === 1) ? color.hex() : color.string();
101
+ return text.toLowerCase();
72
102
  }
73
- switch (propertyDef.parent) {
74
- case 'layer':
75
- // @ts-expect-error: too complex to handle
76
- layer[key] = value;
77
- break;
78
- case 'layout':
79
- if (!layer.layout)
80
- layer.layout = {};
81
- // @ts-expect-error: too complex to handle
82
- layer.layout[key] = value;
83
- break;
84
- case 'paint':
85
- if (!layer.paint)
86
- layer.paint = {};
87
- // @ts-expect-error: too complex to handle
88
- layer.paint[key] = value;
89
- break;
90
- default:
91
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
92
- throw new Error(`unknown parent "${propertyDef.parent}" for key "${key}"`);
103
+ throw new Error(`unknown color type "${typeof value}"`);
104
+ }
105
+ function processFont(value) {
106
+ if (typeof value === 'string')
107
+ return [value];
108
+ throw new Error(`unknown font type "${typeof value}"`);
109
+ }
110
+ function processExpression(value, cbValue) {
111
+ if (typeof value === 'object') {
112
+ if (value instanceof Color)
113
+ return processColor(value);
114
+ if (!Array.isArray(value)) {
115
+ return processZoomStops(value, cbValue);
116
+ }
93
117
  }
94
- });
95
- }
96
- }
97
- function processColor(value) {
98
- if (typeof value === 'string')
99
- value = Color(value);
100
- if (value instanceof Color) {
101
- value = (value.alpha() === 1) ? value.hex() : value.string();
102
- return value.toLowerCase();
103
- }
104
- throw new Error(`unknown color type "${typeof value}"`);
105
- }
106
- function processFont(value) {
107
- if (typeof value === 'string')
108
- return [value];
109
- throw new Error(`unknown font type "${typeof value}"`);
110
- }
111
- function processExpression(value, cbValue) {
112
- if (typeof value === 'object') {
113
- if (value instanceof Color)
114
- return processColor(value);
115
- if (!Array.isArray(value)) {
116
- return processZoomStops(value, cbValue);
118
+ return cbValue ? cbValue(value) : value;
119
+ }
120
+ function processZoomStops(obj, cbValue) {
121
+ return {
122
+ stops: Object.entries(obj)
123
+ .map(([z, v]) => [parseInt(z, 10), cbValue ? cbValue(v) : v])
124
+ .sort((a, b) => a[0] - b[0]),
125
+ };
117
126
  }
118
127
  }
119
- return cbValue ? cbValue(value) : value;
120
- }
121
- function processZoomStops(obj, cbValue) {
122
- return {
123
- stops: Object.entries(obj)
124
- .map(([z, v]) => [parseInt(z, 10), cbValue ? cbValue(v) : v])
125
- .sort((a, b) => a[0] - b[0]),
126
- };
127
128
  }
@@ -1,5 +1,4 @@
1
- import type StyleBuilder from './style_builder.js';
2
- import type { StyleBuilderColors } from './types.js';
1
+ import Color from 'color';
3
2
  export interface RecolorOptions {
4
3
  /** If true, inverts the colors. */
5
4
  invert?: boolean;
@@ -19,4 +18,12 @@ export interface RecolorOptions {
19
18
  tintColor?: string;
20
19
  }
21
20
  export declare function getDefaultRecolorFlags(): RecolorOptions;
22
- export declare function recolor<Subclass extends StyleBuilder<Subclass>>(colors: StyleBuilderColors<Subclass>, opt?: RecolorOptions): void;
21
+ export declare function recolorObject(colors: Record<string, Color>, opt?: RecolorOptions): void;
22
+ export declare class CachedRecolor {
23
+ private readonly skip;
24
+ private readonly opt?;
25
+ private readonly cache;
26
+ constructor(opt?: RecolorOptions);
27
+ do(color: Color): Color;
28
+ }
29
+ export declare function recolor(color: Color, opt?: RecolorOptions): Color;
@@ -11,81 +11,108 @@ export function getDefaultRecolorFlags() {
11
11
  tintColor: '#FF0000',
12
12
  };
13
13
  }
14
- export function recolor(colors, opt) {
14
+ function isValidRecolorOptions(opt) {
15
15
  if (!opt)
16
+ return false;
17
+ if ((opt.invert != null) && opt.invert)
18
+ return true;
19
+ if ((opt.rotate != null) && (opt.rotate !== 0))
20
+ return true;
21
+ if ((opt.saturate != null) && (opt.saturate !== 0))
22
+ return true;
23
+ if ((opt.gamma != null) && (opt.gamma !== 1))
24
+ return true;
25
+ if ((opt.contrast != null) && (opt.contrast !== 1))
26
+ return true;
27
+ if ((opt.brightness != null) && (opt.brightness !== 0))
28
+ return true;
29
+ if ((opt.tint != null) && (opt.tint !== 0))
30
+ return true;
31
+ if ((opt.tintColor != null) && (opt.tintColor !== '#FF0000'))
32
+ return true;
33
+ return false;
34
+ }
35
+ export function recolorObject(colors, opt) {
36
+ if (!isValidRecolorOptions(opt))
16
37
  return;
38
+ for (const [k, c] of Object.entries(colors)) {
39
+ colors[k] = recolor(c, opt);
40
+ }
41
+ }
42
+ export class CachedRecolor {
43
+ skip;
44
+ opt;
45
+ cache;
46
+ constructor(opt) {
47
+ this.skip = !isValidRecolorOptions(opt);
48
+ this.cache = new Map();
49
+ this.opt = opt;
50
+ }
51
+ do(color) {
52
+ if (this.skip)
53
+ return color;
54
+ const key = color.string();
55
+ const result = this.cache.get(key);
56
+ if (result)
57
+ return result;
58
+ color = recolor(color, this.opt);
59
+ this.cache.set(key, color);
60
+ return color;
61
+ }
62
+ }
63
+ export function recolor(color, opt) {
64
+ if (!isValidRecolorOptions(opt))
65
+ return color;
17
66
  if (opt.invert ?? false)
18
- invert();
67
+ color = color.negate();
19
68
  if ((opt.rotate !== undefined) && (opt.rotate !== 0))
20
- rotate(opt.rotate);
69
+ color = color.rotate(opt.rotate);
21
70
  if ((opt.saturate !== undefined) && (opt.saturate !== 0))
22
- saturate(opt.saturate);
71
+ color = color.saturate(opt.saturate);
23
72
  if ((opt.gamma !== undefined) && (opt.gamma !== 1))
24
- gamma(opt.gamma);
73
+ color = gamma(color, opt.gamma);
25
74
  if ((opt.contrast !== undefined) && (opt.contrast !== 1))
26
- contrast(opt.contrast);
75
+ color = contrast(color, opt.contrast);
27
76
  if ((opt.brightness !== undefined) && (opt.brightness !== 0))
28
- brightness(opt.brightness);
77
+ color = brightness(color, opt.brightness);
29
78
  if ((opt.tint !== undefined) && (opt.tintColor !== undefined) && (opt.tint !== 0))
30
- tint(opt.tint, Color(opt.tintColor));
31
- function forEachColor(callback) {
32
- for (const k in colors)
33
- colors[k] = callback(colors[k]);
34
- }
35
- function invert() {
36
- forEachColor(c => c.negate());
37
- }
38
- function rotate(value) {
39
- forEachColor(c => c.rotate(value));
40
- }
41
- function saturate(value) {
42
- forEachColor(c => c.saturate(value));
43
- }
44
- function gamma(value) {
79
+ color = tint(color, opt.tint, Color(opt.tintColor));
80
+ return color;
81
+ function gamma(c, value) {
45
82
  if (value < 1e-3)
46
83
  value = 1e-3;
47
84
  if (value > 1e3)
48
85
  value = 1e3;
49
- forEachColor(color => {
50
- const rgb = color.rgb().array();
51
- return Color.rgb(Math.pow(rgb[0] / 255, value) * 255, Math.pow(rgb[1] / 255, value) * 255, Math.pow(rgb[2] / 255, value) * 255, color.alpha());
52
- });
86
+ const rgb = c.rgb().array();
87
+ return Color.rgb(Math.pow(rgb[0] / 255, value) * 255, Math.pow(rgb[1] / 255, value) * 255, Math.pow(rgb[2] / 255, value) * 255, c.alpha());
53
88
  }
54
- function contrast(value) {
89
+ function contrast(c, value) {
55
90
  if (value < 0)
56
91
  value = 0;
57
92
  if (value > 1e6)
58
93
  value = 1e6;
59
- forEachColor(color => {
60
- const rgb = color.rgb().array();
61
- return Color.rgb((rgb[0] - 127.5) * value + 127.5, (rgb[1] - 127.5) * value + 127.5, (rgb[2] - 127.5) * value + 127.5, color.alpha());
62
- });
94
+ const rgb = c.rgb().array();
95
+ return Color.rgb((rgb[0] - 127.5) * value + 127.5, (rgb[1] - 127.5) * value + 127.5, (rgb[2] - 127.5) * value + 127.5, c.alpha());
63
96
  }
64
- function brightness(value) {
97
+ function brightness(c, value) {
65
98
  if (value < -1e6)
66
99
  value = -1e6;
67
100
  if (value > 1e6)
68
101
  value = 1e6;
69
102
  const a = 1 - Math.abs(value);
70
103
  const b = (value < 0) ? 0 : 255 * value;
71
- forEachColor(color => {
72
- const rgb = color.rgb().array();
73
- return Color.rgb(rgb[0] * a + b, rgb[1] * a + b, rgb[2] * a + b, color.alpha());
74
- });
104
+ const rgb = c.rgb().array();
105
+ return Color.rgb(rgb[0] * a + b, rgb[1] * a + b, rgb[2] * a + b, c.alpha());
75
106
  }
76
- function tint(value, tintColor) {
107
+ function tint(c, value, tintColor) {
77
108
  if (value < 0)
78
109
  value = 0;
79
110
  if (value > 1)
80
111
  value = 1;
81
- const tintColorHSV = tintColor.hsv().array();
82
- forEachColor(color => {
83
- const rgb0 = color.rgb().array();
84
- const hsv = color.hsv().array();
85
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
86
- hsv[0] = tintColorHSV[0];
87
- const rgbNew = Color.hsv(hsv).rgb().array();
88
- return Color.rgb(rgb0[0] * (1 - value) + value * rgbNew[0], rgb0[1] * (1 - value) + value * rgbNew[1], rgb0[2] * (1 - value) + value * rgbNew[2], color.alpha());
89
- });
112
+ const rgb0 = c.rgb().array();
113
+ const hsv = c.hsv().array();
114
+ hsv[0] = tintColor.hue();
115
+ const rgbNew = Color.hsv(hsv).rgb().array();
116
+ return Color.rgb(rgb0[0] * (1 - value) + value * rgbNew[0], rgb0[1] * (1 - value) + value * rgbNew[1], rgb0[2] * (1 - value) + value * rgbNew[2], c.alpha());
90
117
  }
91
118
  }
@@ -1,7 +1,7 @@
1
1
  import Color from 'color';
2
2
  import { getShortbreadTemplate, getShortbreadLayers } from '../shortbread/index.js';
3
3
  import { decorate } from './decorator.js';
4
- import { getDefaultRecolorFlags, recolor } from './recolor.js';
4
+ import { CachedRecolor, getDefaultRecolorFlags } from './recolor.js';
5
5
  import { deepClone, resolveUrl } from '../lib/utils.js';
6
6
  // StyleBuilder class definition
7
7
  export default class StyleBuilder {
@@ -21,8 +21,6 @@ export default class StyleBuilder {
21
21
  for (const key in options.colors)
22
22
  colors[key] = Color(options.colors[key]);
23
23
  }
24
- // transform colors
25
- recolor(colors, recolorOptions);
26
24
  const fonts = deepClone(this.defaultFonts);
27
25
  if (options.fonts) {
28
26
  for (const key in options.fonts) {
@@ -57,7 +55,7 @@ export default class StyleBuilder {
57
55
  throw Error('unknown layer type');
58
56
  });
59
57
  // apply layer rules
60
- layers = decorate(layers, layerStyleRules);
58
+ layers = decorate(layers, layerStyleRules, new CachedRecolor(recolorOptions));
61
59
  // hide labels, if wanted
62
60
  if (hideLabels)
63
61
  layers = layers.filter(l => l.type !== 'symbol');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versatiles/style",
3
- "version": "4.2.8",
3
+ "version": "4.3.0",
4
4
  "description": "Generate StyleJSON for MapLibre",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",