• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / deck.gl / 23392808543

22 Mar 2026 01:13AM UTC coverage: 91.064% (+0.04%) from 91.025%
23392808543

push

github

web-flow
feat(layers): add clipping to TextLayer (#10118)

7072 of 7822 branches covered (90.41%)

Branch coverage included in aggregate %.

330 of 332 new or added lines in 7 files covered. (99.4%)

1 existing line in 1 file now uncovered.

58466 of 64147 relevant lines covered (91.14%)

14005.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.72
/modules/layers/src/text-layer/text-layer.ts
1
// deck.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import {CompositeLayer, createIterable, log} from '@deck.gl/core';
1✔
6
import MultiIconLayer from './multi-icon-layer/multi-icon-layer';
1✔
7
import FontAtlasManager, {
1✔
8
  DEFAULT_FONT_SETTINGS,
1✔
9
  setFontAtlasCacheLimit
1✔
10
} from './font-atlas-manager';
1✔
11
import {transformParagraph, getTextFromBuffer} from './utils';
1✔
12

1✔
13
import TextBackgroundLayer from './text-background-layer/text-background-layer';
1✔
14
import type {ContentAlignModes} from './text-uniforms';
1✔
15

1✔
16
import type {FontSettings} from './font-atlas-manager';
1✔
17
import type {
1✔
18
  LayerProps,
1✔
19
  LayerDataSource,
1✔
20
  Accessor,
1✔
21
  AccessorFunction,
1✔
22
  AccessorContext,
1✔
23
  Unit,
1✔
24
  Position,
1✔
25
  Color,
1✔
26
  UpdateParameters,
1✔
27
  GetPickingInfoParams,
1✔
28
  PickingInfo,
1✔
29
  DefaultProps
1✔
30
} from '@deck.gl/core';
1✔
31

1✔
32
const TEXT_ANCHOR = {
1✔
33
  start: 1,
1✔
34
  middle: 0,
1✔
35
  end: -1
1✔
36
} as const;
1✔
37

1✔
38
const ALIGNMENT_BASELINE = {
1✔
39
  top: 1,
1✔
40
  center: 0,
1✔
41
  bottom: -1
1✔
42
} as const;
1✔
43

1✔
44
const DEFAULT_COLOR = [0, 0, 0, 255] as const;
1✔
45

1✔
46
const DEFAULT_LINE_HEIGHT = 1.0;
1✔
47

1✔
48
type _TextLayerProps<DataT> = {
1✔
49
  data: LayerDataSource<DataT>;
1✔
50
  /** If `true`, the text always faces camera. Otherwise the text faces up (z).
1✔
51
   * @default true
1✔
52
   */
1✔
53
  billboard?: boolean;
1✔
54
  /**
1✔
55
   * Text size multiplier.
1✔
56
   * @default 1
1✔
57
   */
1✔
58
  sizeScale?: number;
1✔
59
  /**
1✔
60
   * The units of the size, one of `'meters'`, `'common'`, and `'pixels'`.
1✔
61
   * @default 'pixels'
1✔
62
   */
1✔
63
  sizeUnits?: Unit;
1✔
64
  /**
1✔
65
   * The minimum size in pixels. When using non-pixel `sizeUnits`, this prop can be used to prevent the icon from getting too small when zoomed out.
1✔
66
   * @default 0
1✔
67
   */
1✔
68
  sizeMinPixels?: number;
1✔
69
  /**
1✔
70
   * The maximum size in pixels. When using non-pixel `sizeUnits`, this prop can be used to prevent the icon from getting too big when zoomed in.
1✔
71
   * @default Number.MAX_SAFE_INTEGER
1✔
72
   */
1✔
73
  sizeMaxPixels?: number;
1✔
74

1✔
75
  /** Whether to render background for the text blocks.
1✔
76
   * @default false
1✔
77
   */
1✔
78
  background?: boolean;
1✔
79
  /** Background color accessor.
1✔
80
   * @default [255, 255, 255, 255]
1✔
81
   */
1✔
82
  getBackgroundColor?: Accessor<DataT, Color>;
1✔
83
  /** Border color accessor.
1✔
84
   * @default [0, 0, 0, 255]
1✔
85
   */
1✔
86
  getBorderColor?: Accessor<DataT, Color>;
1✔
87
  /** Border width accessor.
1✔
88
   * @default 0
1✔
89
   */
1✔
90
  getBorderWidth?: Accessor<DataT, number>;
1✔
91
  /** The border radius of the background.
1✔
92
   * If a number is supplied, it is the same border radius in pixel for all corners.
1✔
93
   * If an array of 4 is supplied, it is interpreted as `[bottom_right_corner, top_right_corner, bottom_left_corner, top_left_corner]` border radius in pixel.
1✔
94
   * @default 0
1✔
95
   */
1✔
96
  backgroundBorderRadius?: number | Readonly<[number, number, number, number]>;
1✔
97
  /**
1✔
98
   * The padding around the text to position the background. Only effective if content box is unset.
1✔
99
   * If an array of 2 is supplied, it is interpreted as `[padding_x, padding_y]` in pixels.
1✔
100
   * If an array of 4 is supplied, it is interpreted as `[padding_left, padding_top, padding_right, padding_bottom]` in pixels.
1✔
101
   * @default [0, 0, 0, 0]
1✔
102
   */
1✔
103
  backgroundPadding?: Readonly<[number, number]> | Readonly<[number, number, number, number]>;
1✔
104
  /**
1✔
105
   * Specifies a list of characters to include in the font. If set to 'auto', will be automatically generated from the data set.
1✔
106
   * @default (ASCII characters 32-128)
1✔
107
   */
1✔
108
  characterSet?: FontSettings['characterSet'] | 'auto';
1✔
109
  /** CSS font family
1✔
110
   * @default 'Monaco, monospace'
1✔
111
   */
1✔
112
  fontFamily?: FontSettings['fontFamily'];
1✔
113
  /** CSS font weight
1✔
114
   * @default 'normal'
1✔
115
   */
1✔
116
  fontWeight?: FontSettings['fontWeight'];
1✔
117
  /** A unitless number that will be multiplied with the current text size to set the line height.
1✔
118
   * @default 'normal'
1✔
119
   */
1✔
120
  lineHeight?: number;
1✔
121
  /**
1✔
122
   * Width of outline around the text, relative to the text size. Only effective if `fontSettings.sdf` is `true`.
1✔
123
   * @default 0
1✔
124
   */
1✔
125
  outlineWidth?: number;
1✔
126
  /**
1✔
127
   * Color of outline around the text, in `[r, g, b, [a]]`. Each channel is a number between 0-255 and `a` is 255 if not supplied.
1✔
128
   * @default [0, 0, 0, 255]
1✔
129
   */
1✔
130
  outlineColor?: Color;
1✔
131
  /**
1✔
132
   * Advance options for fine tuning the appearance and performance of the generated shared `fontAtlas`.
1✔
133
   */
1✔
134
  fontSettings?: FontSettings;
1✔
135
  /**
1✔
136
   * Available options are `break-all` and `break-word`. A valid `maxWidth` has to be provided to use `wordBreak`.
1✔
137
   * @default 'break-word'
1✔
138
   */
1✔
139
  wordBreak?: 'break-word' | 'break-all';
1✔
140
  /**
1✔
141
   * A unitless number that will be multiplied with the current text size to set the width limit of a string.
1✔
142
   * If specified, when the text is longer than the width limit, it will be wrapped into multiple lines using
1✔
143
   * the strategy of `wordBreak`.
1✔
144
   * @default -1
1✔
145
   */
1✔
146
  maxWidth?: number;
1✔
147
  /**
1✔
148
   * Label text accessor
1✔
149
   */
1✔
150
  getText?: AccessorFunction<DataT, string>;
1✔
151
  /**
1✔
152
   * Anchor position accessor
1✔
153
   */
1✔
154
  getPosition?: Accessor<DataT, Position>;
1✔
155
  /**
1✔
156
   * Label color accessor
1✔
157
   * @default [0, 0, 0, 255]
1✔
158
   */
1✔
159
  getColor?: Accessor<DataT, Color>;
1✔
160
  /**
1✔
161
   * Label size accessor
1✔
162
   * @default 32
1✔
163
   */
1✔
164
  getSize?: Accessor<DataT, number>;
1✔
165
  /**
1✔
166
   * Label rotation accessor, in degrees
1✔
167
   * @default 0
1✔
168
   */
1✔
169
  getAngle?: Accessor<DataT, number>;
1✔
170
  /**
1✔
171
   * Horizontal alignment accessor
1✔
172
   * @default 'middle'
1✔
173
   */
1✔
174
  getTextAnchor?: Accessor<DataT, 'start' | 'middle' | 'end'>;
1✔
175
  /**
1✔
176
   * Vertical alignment accessor
1✔
177
   * @default 'center'
1✔
178
   */
1✔
179
  getAlignmentBaseline?: Accessor<DataT, 'top' | 'center' | 'bottom'>;
1✔
180
  /**
1✔
181
   * Label offset from the anchor position, [x, y] in pixels
1✔
182
   * @default [0, 0]
1✔
183
   */
1✔
184
  getPixelOffset?: Accessor<DataT, Readonly<[number, number]>>;
1✔
185
  /**
1✔
186
   * @deprecated Use `background` and `getBackgroundColor` instead
1✔
187
   */
1✔
188
  backgroundColor?: Color;
1✔
189

1✔
190
  /** Container limits for each object, as meter offsets from the anchor position.
1✔
191
   * Characters that overflow the area are not displayed.
1✔
192
   * Use negative width/height to disable clipping.
1✔
193
   * @default [0, 0, -1, -1]
1✔
194
   */
1✔
195
  getContentBox?: Accessor<DataT, [x: number, y: number, width: number, height: number]>;
1✔
196

1✔
197
  /**
1✔
198
   * Minimum visible region of the content box in screen pixels. If the visible width or height is smaller than the specified cutoff, the corresponding text is hidden completely.
1✔
199
   * This prop can be used to set the minimum length of clipped texts to improve readability.
1✔
200
   * @default [0, 0]
1✔
201
   */
1✔
202
  contentCutoffPixels?: [width: number, height: number];
1✔
203

1✔
204
  /**
1✔
205
   * Align the text horizontally to the visible region of the content box.
1✔
206
   * @default 'none'
1✔
207
   */
1✔
208
  contentAlignHorizontal?: ContentAlignModes;
1✔
209

1✔
210
  /**
1✔
211
   * Align the text vertically to the visible region of the content box.
1✔
212
   * @default 'none'
1✔
213
   */
1✔
214
  contentAlignVertical?: ContentAlignModes;
1✔
215
};
1✔
216

1✔
217
export type TextLayerProps<DataT = unknown> = _TextLayerProps<DataT> & LayerProps;
1✔
218

1✔
219
const defaultProps: DefaultProps<TextLayerProps> = {
1✔
220
  billboard: true,
1✔
221
  sizeScale: 1,
1✔
222
  sizeUnits: 'pixels',
1✔
223
  sizeMinPixels: 0,
1✔
224
  sizeMaxPixels: Number.MAX_SAFE_INTEGER,
1✔
225

1✔
226
  background: false,
1✔
227
  getBackgroundColor: {type: 'accessor', value: [255, 255, 255, 255]},
1✔
228
  getBorderColor: {type: 'accessor', value: DEFAULT_COLOR},
1✔
229
  getBorderWidth: {type: 'accessor', value: 0},
1✔
230
  backgroundBorderRadius: {type: 'object', value: 0},
1✔
231
  backgroundPadding: {type: 'array', value: [0, 0, 0, 0]},
1✔
232

1✔
233
  characterSet: {type: 'object', value: DEFAULT_FONT_SETTINGS.characterSet},
1✔
234
  fontFamily: DEFAULT_FONT_SETTINGS.fontFamily,
1✔
235
  fontWeight: DEFAULT_FONT_SETTINGS.fontWeight,
1✔
236
  lineHeight: DEFAULT_LINE_HEIGHT,
1✔
237
  outlineWidth: {type: 'number', value: 0, min: 0},
1✔
238
  outlineColor: {type: 'color', value: DEFAULT_COLOR},
1✔
239
  fontSettings: {type: 'object', value: {}, compare: 1},
1✔
240

1✔
241
  // auto wrapping options
1✔
242
  wordBreak: 'break-word',
1✔
243
  maxWidth: {type: 'number', value: -1},
1✔
244
  contentCutoffPixels: {type: 'array', value: [0, 0]},
1✔
245
  contentAlignHorizontal: 'none',
1✔
246
  contentAlignVertical: 'none',
1✔
247

1✔
248
  getText: {type: 'accessor', value: (x: any) => x.text},
1✔
249
  getPosition: {type: 'accessor', value: (x: any) => x.position},
1✔
250
  getColor: {type: 'accessor', value: DEFAULT_COLOR},
1✔
251
  getSize: {type: 'accessor', value: 32},
1✔
252
  getAngle: {type: 'accessor', value: 0},
1✔
253
  getTextAnchor: {type: 'accessor', value: 'middle'},
1✔
254
  getAlignmentBaseline: {type: 'accessor', value: 'center'},
1✔
255
  getPixelOffset: {type: 'accessor', value: [0, 0]},
1✔
256
  getContentBox: {type: 'accessor', value: [0, 0, -1, -1]},
1✔
257

1✔
258
  // deprecated
1✔
259
  backgroundColor: {deprecatedFor: ['background', 'getBackgroundColor']}
1✔
260
};
1✔
261

1✔
262
/** Render text labels at given coordinates. */
1✔
263
export default class TextLayer<DataT = any, ExtraPropsT extends {} = {}> extends CompositeLayer<
1✔
264
  ExtraPropsT & Required<_TextLayerProps<DataT>>
1✔
265
> {
1✔
266
  static defaultProps = defaultProps;
169✔
267
  static layerName = 'TextLayer';
169✔
268

169✔
269
  state!: {
169✔
270
    styleVersion: number;
169✔
271
    fontAtlasManager: FontAtlasManager;
169✔
272
    characterSet?: Set<string>;
169✔
273
    startIndices?: number[];
169✔
274
    numInstances?: number;
169✔
275
    getText?: AccessorFunction<DataT, string>;
169✔
276
  };
169✔
277

169✔
278
  initializeState() {
169✔
279
    this.state = {
169✔
280
      styleVersion: 0,
169✔
281
      fontAtlasManager: new FontAtlasManager()
169✔
282
    };
169✔
283

169✔
284
    // Breaking change in v8.9
169✔
285
    if (this.props.maxWidth > 0) {
169!
286
      log.once(1, 'v8.9 breaking change: TextLayer maxWidth is now relative to text size')();
×
287
    }
×
288
  }
169✔
289

169✔
290
  // eslint-disable-next-line complexity
169✔
291
  updateState(params: UpdateParameters<this>) {
169✔
292
    const {props, oldProps, changeFlags} = params;
107✔
293
    const textChanged =
107✔
294
      changeFlags.dataChanged ||
107✔
295
      (changeFlags.updateTriggersChanged &&
84✔
296
        (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getText));
60✔
297

107✔
298
    if (textChanged) {
107✔
299
      this._updateText();
24✔
300
    }
24✔
301

107✔
302
    const fontChanged = this._updateFontAtlas();
107✔
303

107✔
304
    const styleChanged =
107✔
305
      fontChanged ||
107✔
306
      props.lineHeight !== oldProps.lineHeight ||
94✔
307
      props.wordBreak !== oldProps.wordBreak ||
88✔
308
      props.maxWidth !== oldProps.maxWidth;
88✔
309

107✔
310
    if (styleChanged) {
107✔
311
      this.setState({
24✔
312
        styleVersion: this.state.styleVersion + 1
24✔
313
      });
24✔
314
    }
24✔
315
  }
107✔
316

169✔
317
  getPickingInfo({info}: GetPickingInfoParams): PickingInfo {
169✔
318
    // because `TextLayer` assign the same pickingInfoIndex for one text label,
×
319
    // here info.index refers the index of text label in props.data
×
320
    info.object = info.index >= 0 ? (this.props.data as any[])[info.index] : null;
×
321
    return info;
×
322
  }
×
323

169✔
324
  /** Returns true if font has changed */
169✔
325
  private _updateFontAtlas(): boolean {
169✔
326
    const {fontSettings, fontFamily, fontWeight} = this.props;
107✔
327
    const {fontAtlasManager, characterSet} = this.state;
107✔
328

107✔
329
    const fontProps = {
107✔
330
      ...fontSettings,
107✔
331
      characterSet,
107✔
332
      fontFamily,
107✔
333
      fontWeight
107✔
334
    };
107✔
335

107✔
336
    if (!fontAtlasManager.mapping) {
107✔
337
      // This is the first update
12✔
338
      fontAtlasManager.setProps(fontProps);
12✔
339
      return true;
12✔
340
    }
12✔
341

95✔
342
    for (const key in fontProps) {
107✔
343
      if (fontProps[key] !== fontAtlasManager.props[key]) {
283✔
344
        fontAtlasManager.setProps(fontProps);
1✔
345
        return true;
1✔
346
      }
1✔
347
    }
283✔
348

94✔
349
    return false;
94✔
350
  }
107✔
351

169✔
352
  // Text strings are variable width objects
169✔
353
  // Count characters and start offsets
169✔
354
  private _updateText() {
169✔
355
    const {data, characterSet} = this.props;
24✔
356
    const textBuffer = (data as any).attributes?.getText;
24✔
357
    let {getText} = this.props;
24✔
358
    let startIndices: number[] = (data as any).startIndices;
24✔
359
    let numInstances: number;
24✔
360

24✔
361
    const autoCharacterSet = characterSet === 'auto' && new Set();
24✔
362

24✔
363
    if (textBuffer && startIndices) {
24✔
364
      const {texts, characterCount} = getTextFromBuffer({
3✔
365
        ...(ArrayBuffer.isView(textBuffer) ? {value: textBuffer} : textBuffer),
3✔
366
        // @ts-ignore if data.attribute is defined then length is expected
3✔
367
        length: data.length,
3✔
368
        startIndices,
3✔
369
        characterSet: autoCharacterSet
3✔
370
      });
3✔
371
      numInstances = characterCount;
3✔
372
      getText = (_, {index}) => texts[index];
3✔
373
    } else {
24✔
374
      const {iterable, objectInfo} = createIterable(data);
21✔
375
      startIndices = [0];
21✔
376
      numInstances = 0;
21✔
377

21✔
378
      for (const object of iterable) {
21✔
379
        objectInfo.index++;
27,744✔
380
        // Break into an array of characters
27,744✔
381
        // When dealing with double-length unicode characters, `str.length` or `str[i]` do not work
27,744✔
382
        const text = Array.from(getText(object, objectInfo) || '');
27,744✔
383
        if (autoCharacterSet) {
27,744✔
384
          // eslint-disable-next-line @typescript-eslint/unbound-method
3✔
385
          text.forEach(autoCharacterSet.add, autoCharacterSet);
3✔
386
        }
3✔
387
        numInstances += text.length;
27,744✔
388
        startIndices.push(numInstances);
27,744✔
389
      }
27,744✔
390
    }
21✔
391

24✔
392
    this.setState({
24✔
393
      getText,
24✔
394
      startIndices,
24✔
395
      numInstances,
24✔
396
      characterSet: autoCharacterSet || characterSet
24✔
397
    });
24✔
398
  }
24✔
399

169✔
400
  /** There are two size systems in this layer:
169✔
401

169✔
402
    + Pixel size: user-specified text size, via getSize, sizeScale, sizeUnits etc.
169✔
403
      The layer roughly matches the output of the layer to CSS pixels, e.g. getSize: 12, sizeScale: 2
169✔
404
      in layer props is roughly equivalent to font-size: 24px in CSS.
169✔
405
    + Texture size: internally, character positions in a text blob are calculated using the sizes of iconMapping,
169✔
406
      which depends on how large each character is drawn into the font atlas. This is controlled by
169✔
407
      fontSettings.fontSize (default 64) and most users do not set it manually.
169✔
408
      These numbers are intended to be used in the vertex shader and never to be exposed to the end user.
169✔
409

169✔
410
    All surfaces exposed to the user should either use the pixel size or a multiplier relative to the pixel size. */
169✔
411

169✔
412
  /** Calculate the size and position of each character in a text string.
169✔
413
   * Values are in texture size */
169✔
414
  private transformParagraph(
169✔
415
    object: DataT,
136,230✔
416
    objectInfo: AccessorContext<DataT>
136,230✔
417
  ): ReturnType<typeof transformParagraph> {
136,230✔
418
    const {fontAtlasManager} = this.state;
136,230✔
419
    const iconMapping = fontAtlasManager.mapping!;
136,230✔
420
    const getText = this.state.getText!;
136,230✔
421
    const {wordBreak, lineHeight, maxWidth} = this.props;
136,230✔
422

136,230✔
423
    const paragraph = getText(object, objectInfo) || '';
136,230✔
424
    return transformParagraph(
136,230✔
425
      paragraph,
136,230✔
426
      lineHeight,
136,230✔
427
      wordBreak,
136,230✔
428
      maxWidth * fontAtlasManager.props.fontSize,
136,230✔
429
      iconMapping
136,230✔
430
    );
136,230✔
431
  }
136,230✔
432

169✔
433
  /** Returns the x, y, width, height of each text string, relative to pixel size.
169✔
434
   * Used to render the background.
169✔
435
   */
169✔
436
  private getBoundingRect: AccessorFunction<DataT, [number, number, number, number]> = (
169✔
437
    object,
65,588✔
438
    objectInfo
65,588✔
439
  ) => {
65,588✔
440
    let {
65,588✔
441
      size: [width, height]
65,588✔
442
    } = this.transformParagraph(object, objectInfo);
65,588✔
443
    const {fontSize} = this.state.fontAtlasManager.props;
65,588✔
444
    width /= fontSize;
65,588✔
445
    height /= fontSize;
65,588✔
446

65,588✔
447
    const {getTextAnchor, getAlignmentBaseline} = this.props;
65,588✔
448
    const anchorX =
65,588✔
449
      TEXT_ANCHOR[
65,588✔
450
        typeof getTextAnchor === 'function' ? getTextAnchor(object, objectInfo) : getTextAnchor
65,588✔
451
      ];
65,588✔
452
    const anchorY =
65,588✔
453
      ALIGNMENT_BASELINE[
65,588✔
454
        typeof getAlignmentBaseline === 'function'
65,588✔
455
          ? getAlignmentBaseline(object, objectInfo)
10,092✔
456
          : getAlignmentBaseline
55,496✔
457
      ];
65,588✔
458

65,588✔
459
    return [((anchorX - 1) * width) / 2, ((anchorY - 1) * height) / 2, width, height];
65,588✔
460
  };
65,588✔
461

169✔
462
  /** Returns the x, y offsets of each character in a text string, in texture size.
169✔
463
   * Used to layout characters in the vertex shader.
169✔
464
   */
169✔
465
  private getIconOffsets: AccessorFunction<DataT, number[]> = (object, objectInfo) => {
169✔
466
    const {getTextAnchor, getAlignmentBaseline} = this.props;
70,642✔
467

70,642✔
468
    const {
70,642✔
469
      x,
70,642✔
470
      y,
70,642✔
471
      rowWidth,
70,642✔
472
      size: [width, height]
70,642✔
473
    } = this.transformParagraph(object, objectInfo);
70,642✔
474
    const anchorX =
70,642✔
475
      TEXT_ANCHOR[
70,642✔
476
        typeof getTextAnchor === 'function' ? getTextAnchor(object, objectInfo) : getTextAnchor
70,642✔
477
      ];
70,642✔
478
    const anchorY =
70,642✔
479
      ALIGNMENT_BASELINE[
70,642✔
480
        typeof getAlignmentBaseline === 'function'
70,642✔
481
          ? getAlignmentBaseline(object, objectInfo)
10,092✔
482
          : getAlignmentBaseline
60,550✔
483
      ];
70,642✔
484

70,642✔
485
    const numCharacters = x.length;
70,642✔
486
    const offsets = new Array(numCharacters * 2);
70,642✔
487
    let index = 0;
70,642✔
488

70,642✔
489
    for (let i = 0; i < numCharacters; i++) {
70,642✔
490
      // For a multi-line object, offset in x-direction needs consider
974,294✔
491
      // the row offset in the paragraph and the object offset in the row
974,294✔
492
      const rowOffset = ((1 - anchorX) * (width - rowWidth[i])) / 2;
974,294✔
493
      offsets[index++] = ((anchorX - 1) * width) / 2 + rowOffset + x[i];
974,294✔
494
      offsets[index++] = ((anchorY - 1) * height) / 2 + y[i];
974,294✔
495
    }
974,294✔
496
    return offsets;
70,642✔
497
  };
70,642✔
498

1✔
499
  renderLayers() {
1✔
500
    const {
107✔
501
      startIndices,
107✔
502
      numInstances,
107✔
503
      getText,
107✔
504
      fontAtlasManager: {scale, atlas, mapping},
107✔
505
      styleVersion
107✔
506
    } = this.state;
107✔
507

107✔
508
    const {
107✔
509
      data,
107✔
510
      _dataDiff,
107✔
511
      getPosition,
107✔
512
      getColor,
107✔
513
      getSize,
107✔
514
      getAngle,
107✔
515
      getPixelOffset,
107✔
516
      getBackgroundColor,
107✔
517
      getBorderColor,
107✔
518
      getBorderWidth,
107✔
519
      getContentBox,
107✔
520
      backgroundBorderRadius,
107✔
521
      backgroundPadding,
107✔
522
      background,
107✔
523
      billboard,
107✔
524
      fontSettings,
107✔
525
      outlineWidth,
107✔
526
      outlineColor,
107✔
527
      sizeScale,
107✔
528
      sizeUnits,
107✔
529
      sizeMinPixels,
107✔
530
      sizeMaxPixels,
107✔
531
      contentCutoffPixels,
107✔
532
      contentAlignHorizontal,
107✔
533
      contentAlignVertical,
107✔
534
      transitions,
107✔
535
      updateTriggers
107✔
536
    } = this.props;
107✔
537

107✔
538
    const CharactersLayerClass = this.getSubLayerClass('characters', MultiIconLayer);
107✔
539
    const BackgroundLayerClass = this.getSubLayerClass('background', TextBackgroundLayer);
107✔
540

107✔
541
    return [
107✔
542
      background &&
107✔
543
        new BackgroundLayerClass(
94✔
544
          {
94✔
545
            // background props
94✔
546
            getFillColor: getBackgroundColor,
94✔
547
            getLineColor: getBorderColor,
94✔
548
            getLineWidth: getBorderWidth,
94✔
549
            borderRadius: backgroundBorderRadius,
94✔
550
            padding: backgroundPadding,
94✔
551

94✔
552
            // props shared with characters layer
94✔
553
            getPosition,
94✔
554
            getSize,
94✔
555
            getAngle,
94✔
556
            getPixelOffset,
94✔
557
            getClipRect: getContentBox,
94✔
558
            billboard,
94✔
559
            sizeScale,
94✔
560
            sizeUnits,
94✔
561
            sizeMinPixels,
94✔
562
            sizeMaxPixels,
94✔
563

94✔
564
            transitions: transitions && {
94!
565
              getPosition: transitions.getPosition,
×
566
              getAngle: transitions.getAngle,
×
567
              getSize: transitions.getSize,
×
568
              getFillColor: transitions.getBackgroundColor,
×
569
              getLineColor: transitions.getBorderColor,
×
570
              getLineWidth: transitions.getBorderWidth,
×
571
              getPixelOffset: transitions.getPixelOffset
×
572
            }
×
573
          },
94✔
574
          this.getSubLayerProps({
94✔
575
            id: 'background',
94✔
576
            updateTriggers: {
94✔
577
              getPosition: updateTriggers.getPosition,
94✔
578
              getAngle: updateTriggers.getAngle,
94✔
579
              getSize: updateTriggers.getSize,
94✔
580
              getFillColor: updateTriggers.getBackgroundColor,
94✔
581
              getLineColor: updateTriggers.getBorderColor,
94✔
582
              getLineWidth: updateTriggers.getBorderWidth,
94✔
583
              getPixelOffset: updateTriggers.getPixelOffset,
94✔
584
              getBoundingRect: {
94✔
585
                getText: updateTriggers.getText,
94✔
586
                getTextAnchor: updateTriggers.getTextAnchor,
94✔
587
                getAlignmentBaseline: updateTriggers.getAlignmentBaseline,
94✔
588
                styleVersion
94✔
589
              }
94✔
590
            }
94✔
591
          }),
94✔
592
          {
94✔
593
            data:
94✔
594
              // @ts-ignore (2339) attribute is not defined on all data types
94✔
595
              data.attributes && data.attributes.background
94!
596
                ? // @ts-ignore (2339) attribute is not defined on all data types
×
597
                  {length: data.length, attributes: data.attributes.background}
×
598
                : data,
94✔
599
            _dataDiff,
94✔
600
            // Maintain the same background behavior as <=8.3. Remove in v9?
94✔
601
            autoHighlight: false,
94✔
602
            getBoundingRect: this.getBoundingRect
94✔
603
          }
94✔
604
        ),
94✔
605
      new CharactersLayerClass(
107✔
606
        {
107✔
607
          sdf: fontSettings.sdf,
107✔
608
          smoothing: Number.isFinite(fontSettings.smoothing)
107!
609
            ? fontSettings.smoothing
×
610
            : DEFAULT_FONT_SETTINGS.smoothing,
107✔
611
          outlineWidth: outlineWidth / (fontSettings.radius || DEFAULT_FONT_SETTINGS.radius),
107✔
612
          outlineColor,
107✔
613
          iconAtlas: atlas,
107✔
614
          iconMapping: mapping,
107✔
615

107✔
616
          getPosition,
107✔
617
          getColor,
107✔
618
          getSize,
107✔
619
          getAngle,
107✔
620
          getPixelOffset,
107✔
621
          getContentBox,
107✔
622

107✔
623
          billboard,
107✔
624
          sizeScale: sizeScale * scale,
107✔
625
          sizeUnits,
107✔
626
          sizeMinPixels: sizeMinPixels * scale,
107✔
627
          sizeMaxPixels: sizeMaxPixels * scale,
107✔
628
          contentCutoffPixels,
107✔
629
          contentAlignHorizontal,
107✔
630
          contentAlignVertical,
107✔
631

107✔
632
          transitions: transitions && {
107!
633
            getPosition: transitions.getPosition,
×
634
            getAngle: transitions.getAngle,
×
635
            getColor: transitions.getColor,
×
636
            getSize: transitions.getSize,
×
NEW
637
            getPixelOffset: transitions.getPixelOffset,
×
NEW
638
            getContentBox: transitions.getContentBox
×
UNCOV
639
          }
×
640
        },
107✔
641
        this.getSubLayerProps({
107✔
642
          id: 'characters',
107✔
643
          updateTriggers: {
107✔
644
            all: updateTriggers.getText,
107✔
645
            getPosition: updateTriggers.getPosition,
107✔
646
            getAngle: updateTriggers.getAngle,
107✔
647
            getColor: updateTriggers.getColor,
107✔
648
            getSize: updateTriggers.getSize,
107✔
649
            getPixelOffset: updateTriggers.getPixelOffset,
107✔
650
            getContentBox: updateTriggers.getContentBox,
107✔
651
            getIconOffsets: {
107✔
652
              getTextAnchor: updateTriggers.getTextAnchor,
107✔
653
              getAlignmentBaseline: updateTriggers.getAlignmentBaseline,
107✔
654
              styleVersion
107✔
655
            }
107✔
656
          }
107✔
657
        }),
107✔
658
        {
107✔
659
          data,
107✔
660
          _dataDiff,
107✔
661
          startIndices,
107✔
662
          numInstances,
107✔
663
          getIconOffsets: this.getIconOffsets,
107✔
664
          getIcon: getText
107✔
665
        }
107✔
666
      )
107✔
667
    ];
107✔
668
  }
107✔
669

1✔
670
  static set fontAtlasCacheLimit(limit: number) {
1✔
671
    setFontAtlasCacheLimit(limit);
5✔
672
  }
5✔
673
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc