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

visgl / deck.gl / 23689953389

28 Mar 2026 05:00PM UTC coverage: 79.878% (-11.0%) from 90.907%
23689953389

push

github

web-flow
chore(test): vitest migration (#9969)

3050 of 3709 branches covered (82.23%)

Branch coverage included in aggregate %.

131 of 169 new or added lines in 11 files covered. (77.51%)

171 existing lines in 60 files now uncovered.

13988 of 17621 relevant lines covered (79.38%)

26917.67 hits per line

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

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

5
import {Layer, project32, picking, UNIT} from '@deck.gl/core';
6
import {Geometry} from '@luma.gl/engine';
7
import {Model} from '@luma.gl/engine';
8

9
import {TextBackgroundProps, textBackgroundUniforms} from './text-background-layer-uniforms';
10
import {TextModuleProps, textUniforms} from '../text-uniforms';
11
import vs from './text-background-layer-vertex.glsl';
12
import fs from './text-background-layer-fragment.glsl';
13

14
import type {
15
  LayerProps,
16
  LayerDataSource,
17
  Accessor,
18
  Unit,
19
  Position,
20
  Color,
21
  UpdateParameters,
22
  DefaultProps
23
} from '@deck.gl/core';
24

25
type _TextBackgroundLayerProps<DataT> = {
26
  data: LayerDataSource<DataT>;
27
  billboard?: boolean;
28
  sizeScale?: number;
29
  sizeUnits?: Unit;
30
  sizeMinPixels?: number;
31
  sizeMaxPixels?: number;
32

33
  borderRadius?: number | Readonly<[number, number, number, number]>;
34
  padding?: Readonly<[number, number]> | Readonly<[number, number, number, number]>;
35

36
  getPosition?: Accessor<DataT, Position>;
37
  getSize?: Accessor<DataT, number>;
38
  getAngle?: Accessor<DataT, number>;
39
  getPixelOffset?: Accessor<DataT, Readonly<[number, number]>>;
40
  getBoundingRect?: Accessor<DataT, Readonly<[number, number, number, number]>>;
41
  getClipRect?: Accessor<DataT, [x: number, y: number, width: number, height: number]>;
42
  getFillColor?: Accessor<DataT, Color>;
43
  getLineColor?: Accessor<DataT, Color>;
44
  getLineWidth?: Accessor<DataT, number>;
45
};
46

47
export type TextBackgroundLayerProps<DataT = unknown> = _TextBackgroundLayerProps<DataT> &
48
  LayerProps;
49

50
const defaultProps: DefaultProps<TextBackgroundLayerProps> = {
4✔
51
  billboard: true,
52
  sizeScale: 1,
53
  sizeUnits: 'pixels',
54
  sizeMinPixels: 0,
55
  sizeMaxPixels: Number.MAX_SAFE_INTEGER,
56

57
  borderRadius: {type: 'object', value: 0},
58
  padding: {type: 'array', value: [0, 0, 0, 0]},
59

UNCOV
60
  getPosition: {type: 'accessor', value: (x: any) => x.position},
×
61
  getSize: {type: 'accessor', value: 1},
62
  getAngle: {type: 'accessor', value: 0},
63
  getPixelOffset: {type: 'accessor', value: [0, 0]},
64
  getBoundingRect: {type: 'accessor', value: [0, 0, 0, 0]},
65
  getClipRect: {type: 'accessor', value: [0, 0, -1, -1]},
66
  getFillColor: {type: 'accessor', value: [0, 0, 0, 255]},
67
  getLineColor: {type: 'accessor', value: [0, 0, 0, 255]},
68
  getLineWidth: {type: 'accessor', value: 1}
69
};
70

71
export default class TextBackgroundLayer<DataT = any, ExtraPropsT extends {} = {}> extends Layer<
72
  ExtraPropsT & Required<_TextBackgroundLayerProps<DataT>>
73
> {
74
  static defaultProps = defaultProps;
4✔
75
  static layerName = 'TextBackgroundLayer';
4✔
76

77
  state!: {
78
    model?: Model;
79
  };
80

81
  getShaders() {
82
    return super.getShaders({
6✔
83
      vs,
84
      fs,
85
      modules: [project32, picking, textBackgroundUniforms, textUniforms]
86
    });
87
  }
88

89
  initializeState() {
90
    this.getAttributeManager()!.addInstanced({
5✔
91
      instancePositions: {
92
        size: 3,
93
        type: 'float64',
94
        fp64: this.use64bitPositions(),
95
        transition: true,
96
        accessor: 'getPosition'
97
      },
98
      instanceSizes: {
99
        size: 1,
100
        transition: true,
101
        accessor: 'getSize',
102
        defaultValue: 1
103
      },
104
      instanceAngles: {
105
        size: 1,
106
        transition: true,
107
        accessor: 'getAngle'
108
      },
109
      instanceRects: {
110
        size: 4,
111
        accessor: 'getBoundingRect'
112
      },
113
      instanceClipRect: {
114
        size: 4,
115
        accessor: 'getClipRect',
116
        defaultValue: [0, 0, -1, -1]
117
      },
118
      instancePixelOffsets: {
119
        size: 2,
120
        transition: true,
121
        accessor: 'getPixelOffset'
122
      },
123
      instanceFillColors: {
124
        size: 4,
125
        transition: true,
126
        type: 'unorm8',
127
        accessor: 'getFillColor',
128
        defaultValue: [0, 0, 0, 255]
129
      },
130
      instanceLineColors: {
131
        size: 4,
132
        transition: true,
133
        type: 'unorm8',
134
        accessor: 'getLineColor',
135
        defaultValue: [0, 0, 0, 255]
136
      },
137
      instanceLineWidths: {
138
        size: 1,
139
        transition: true,
140
        accessor: 'getLineWidth',
141
        defaultValue: 1
142
      }
143
    });
144
  }
145

146
  updateState(params: UpdateParameters<this>) {
147
    super.updateState(params);
89✔
148
    const {changeFlags} = params;
89✔
149
    if (changeFlags.extensionsChanged) {
89✔
150
      this.state.model?.destroy();
5✔
151
      this.state.model = this._getModel();
5✔
152
      this.getAttributeManager()!.invalidateAll();
5✔
153
    }
154
  }
155

156
  draw({uniforms}) {
157
    const {billboard, sizeScale, sizeUnits, sizeMinPixels, sizeMaxPixels, getLineWidth} =
158
      this.props;
133✔
159
    let {padding, borderRadius} = this.props;
133✔
160

161
    if (padding.length < 4) {
133✔
162
      padding = [padding[0], padding[1], padding[0], padding[1]];
2✔
163
    }
164

165
    if (!Array.isArray(borderRadius)) {
133✔
166
      borderRadius = [
131✔
167
        borderRadius as number,
168
        borderRadius as number,
169
        borderRadius as number,
170
        borderRadius as number
171
      ];
172
    }
173

174
    const model = this.state.model!;
133✔
175
    const textBackgroundProps: TextBackgroundProps = {
133✔
176
      billboard,
177
      stroked: Boolean(getLineWidth),
178
      borderRadius: borderRadius as [number, number, number, number],
179
      padding: padding as [number, number, number, number],
180
      sizeUnits: UNIT[sizeUnits],
181
      sizeScale,
182
      sizeMinPixels,
183
      sizeMaxPixels
184
    };
185
    const textProps: TextModuleProps = {
133✔
186
      viewport: this.context.viewport
187
    };
188
    model.shaderInputs.setProps({textBackground: textBackgroundProps, text: textProps});
133✔
189
    model.draw(this.context.renderPass);
133✔
190
  }
191

192
  protected _getModel(): Model {
193
    // a square that minimally cover the unit circle
194
    const positions = [0, 0, 1, 0, 0, 1, 1, 1];
5✔
195

196
    return new Model(this.context.device, {
5✔
197
      ...this.getShaders(),
198
      id: this.props.id,
199
      bufferLayout: this.getAttributeManager()!.getBufferLayouts(),
200
      geometry: new Geometry({
201
        topology: 'triangle-strip',
202
        vertexCount: 4,
203
        attributes: {
204
          positions: {size: 2, value: new Float32Array(positions)}
205
        }
206
      }),
207
      isInstanced: true
208
    });
209
  }
210
}
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