Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

uber / deck.gl / 13074

28 Aug 2019 - 0:36 coverage decreased (-2.5%) to 80.994%
13074

Pull #3496

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
use timeline
Pull Request #3496: Transition system refactor (1/2): use timeline

3394 of 4574 branches covered (74.2%)

Branch coverage included in aggregate %.

54 of 58 new or added lines in 9 files covered. (93.1%)

818 existing lines in 101 files now uncovered.

6953 of 8201 relevant lines covered (84.78%)

4628.87 hits per line

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

82.81
/modules/layers/src/text-layer/font-atlas-manager.js
1
/* global document */
10×
2

3
import {Texture2D} from '@luma.gl/core';
4
import TinySDF from '@mapbox/tiny-sdf';
5
import GL from '@luma.gl/constants';
6

7
import {buildMapping} from './utils';
8
import LRUCache from './lru-cache';
9

10
function getDefaultCharacterSet() {
11
  const charSet = [];
1×
12
  for (let i = 32; i < 128; i++) {
1×
13
    charSet.push(String.fromCharCode(i));
96×
14
  }
15
  return charSet;
1×
16
}
17

18
export const DEFAULT_CHAR_SET = getDefaultCharacterSet();
1×
19
export const DEFAULT_FONT_FAMILY = 'Monaco, monospace';
1×
20
export const DEFAULT_FONT_WEIGHT = 'normal';
1×
21
export const DEFAULT_FONT_SIZE = 64;
1×
22
export const DEFAULT_BUFFER = 2;
1×
23
export const DEFAULT_CUTOFF = 0.25;
1×
24
export const DEFAULT_RADIUS = 3;
1×
25

26
const GL_TEXTURE_WRAP_S = 0x2802;
1×
27
const GL_TEXTURE_WRAP_T = 0x2803;
1×
28
const GL_CLAMP_TO_EDGE = 0x812f;
1×
29
const MAX_CANVAS_WIDTH = 1024;
1×
30

31
const BASELINE_SCALE = 0.9;
1×
32
const HEIGHT_SCALE = 1.2;
1×
33

34
// only preserve latest three fontAtlas
35
const CACHE_LIMIT = 3;
1×
36

37
/**
38
 * [key]: {
39
 *   xOffset, // x position of last character in mapping
40
 *   yOffset, // y position of last character in mapping
41
 *   mapping, // x, y coordinate of each character in shared `fontAtlas`
42
 *   data, // canvas
43
 *   width. // canvas.width,
44
 *   height, // canvas.height
45
 * }
46
 *
47
 */
48
const cache = new LRUCache(CACHE_LIMIT);
1×
49

50
const VALID_PROPS = [
1×
51
  'fontFamily',
52
  'fontWeight',
53
  'characterSet',
54
  'fontSize',
55
  'sdf',
56
  'buffer',
57
  'cutoff',
58
  'radius'
59
];
60

61
/**
62
 * get all the chars not in cache
63
 * @param key cache key
64
 * @param characterSet (Array|Set)
65
 * @returns {Array} chars not in cache
66
 */
67
function getNewChars(key, characterSet) {
68
  const cachedFontAtlas = cache.get(key);
4×
69
  if (!cachedFontAtlas) {
4×
70
    return characterSet;
1×
71
  }
72

73
  const newChars = [];
3×
74
  const cachedMapping = cachedFontAtlas.mapping;
3×
75
  let cachedCharSet = Object.keys(cachedMapping);
3×
76
  cachedCharSet = new Set(cachedCharSet);
3×
77

78
  let charSet = characterSet;
3×
79
  if (charSet instanceof Array) {
Branches [[1, 1]] missed. 3×
80
    charSet = new Set(charSet);
3×
81
  }
82

83
  charSet.forEach(char => {
3×
84
    if (!cachedCharSet.has(char)) {
Branches [[2, 0]] missed. 288×
UNCOV
85
      newChars.push(char);
!
86
    }
87
  });
88

89
  return newChars;
3×
90
}
91

92
function populateAlphaChannel(alphaChannel, imageData) {
93
  // populate distance value from tinySDF to image alpha channel
UNCOV
94
  for (let i = 0; i < alphaChannel.length; i++) {
!
UNCOV
95
    imageData.data[4 * i + 3] = alphaChannel[i];
!
96
  }
97
}
98

99
function setTextStyle(ctx, fontFamily, fontSize, fontWeight) {
100
  ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
2×
101
  ctx.fillStyle = '#000';
2×
102
  ctx.textBaseline = 'baseline';
2×
103
  ctx.textAlign = 'left';
2×
104
}
105

106
export default class FontAtlasManager {
107
  constructor(gl) {
108
    this.gl = gl;
4×
109

110
    // font settings
111
    this.props = {
4×
112
      fontFamily: DEFAULT_FONT_FAMILY,
113
      fontWeight: DEFAULT_FONT_WEIGHT,
114
      characterSet: DEFAULT_CHAR_SET,
115
      fontSize: DEFAULT_FONT_SIZE,
116
      buffer: DEFAULT_BUFFER,
117
      // sdf only props
118
      // https://github.com/mapbox/tiny-sdf
119
      sdf: false,
120
      cutoff: DEFAULT_CUTOFF,
121
      radius: DEFAULT_RADIUS
122
    };
123

124
    // key is used for caching generated fontAtlas
125
    this._key = null;
4×
126
    this._texture = new Texture2D(this.gl);
4×
127
  }
128

129
  finalize() {
130
    this._texture.delete();
2×
131
  }
132

133
  get texture() {
134
    return this._texture;
4×
135
  }
136

137
  get mapping() {
138
    const data = cache.get(this._key);
4×
139
    return data && data.mapping;
4×
140
  }
141

142
  get scale() {
143
    return HEIGHT_SCALE;
4×
144
  }
145

146
  setProps(props = {}) {
Branches [[4, 0]] missed.
147
    VALID_PROPS.forEach(prop => {
4×
148
      if (prop in props) {
Branches [[5, 1]] missed. 32×
149
        this.props[prop] = props[prop];
32×
150
      }
151
    });
152

153
    // update cache key
154
    const oldKey = this._key;
4×
155
    this._key = this._getKey();
4×
156

157
    const charSet = getNewChars(this._key, this.props.characterSet);
4×
158
    const cachedFontAtlas = cache.get(this._key);
4×
159

160
    // if a fontAtlas associated with the new settings is cached and
161
    // there are no new chars
162
    if (cachedFontAtlas && charSet.length === 0) {
4×
163
      // update texture with cached fontAtlas
164
      if (this._key !== oldKey) {
Branches [[8, 1]] missed. 3×
165
        this._updateTexture(cachedFontAtlas);
3×
166
      }
167
      return;
3×
168
    }
169

170
    // update fontAtlas with new settings
171
    const fontAtlas = this._generateFontAtlas(this._key, charSet, cachedFontAtlas);
1×
172
    this._updateTexture(fontAtlas);
1×
173

174
    // update cache
175
    cache.set(this._key, fontAtlas);
1×
176
  }
177

178
  _updateTexture({data: canvas, width, height}) {
179
    // resize texture
180
    if (this._texture.width !== width || this._texture.height !== height) {
Branches [[9, 1], [10, 1]] missed. 4×
181
      this._texture.resize({width, height});
4×
182
    }
183

184
    // update image data
185
    this._texture.setImageData({
4×
186
      data: canvas,
187
      width,
188
      height,
189
      parameters: {
190
        [GL_TEXTURE_WRAP_S]: GL_CLAMP_TO_EDGE,
191
        [GL_TEXTURE_WRAP_T]: GL_CLAMP_TO_EDGE,
192
        [GL.UNPACK_FLIP_Y_WEBGL]: true
193
      }
194
    });
195

196
    // this is required step after texture data changed
197
    this._texture.generateMipmap();
4×
198
  }
199

200
  _generateFontAtlas(key, characterSet, cachedFontAtlas) {
201
    const {fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff} = this.props;
1×
202
    let canvas = cachedFontAtlas && cachedFontAtlas.data;
Branches [[11, 1]] missed. 1×
203
    if (!canvas) {
Branches [[12, 1]] missed. 1×
204
      canvas = document.createElement('canvas');
1×
205
      canvas.width = MAX_CANVAS_WIDTH;
1×
206
    }
207
    const ctx = canvas.getContext('2d');
1×
208

209
    setTextStyle(ctx, fontFamily, fontSize, fontWeight);
1×
210

211
    // 1. build mapping
212
    const {mapping, canvasHeight, xOffset, yOffset} = buildMapping(
1×
213
      Object.assign(
214
        {
215
          getFontWidth: char => ctx.measureText(char).width,
96×
216
          fontHeight: fontSize * HEIGHT_SCALE,
217
          buffer,
218
          characterSet,
219
          maxCanvasWidth: MAX_CANVAS_WIDTH
220
        },
221
        cachedFontAtlas && {
Branches [[13, 1]] missed.
222
          mapping: cachedFontAtlas.mapping,
223
          xOffset: cachedFontAtlas.xOffset,
224
          yOffset: cachedFontAtlas.yOffset
225
        }
226
      )
227
    );
228

229
    // 2. update canvas
230
    // copy old canvas data to new canvas only when height changed
231
    if (canvas.height !== canvasHeight) {
Branches [[14, 1]] missed. 1×
232
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
1×
233
      canvas.height = canvasHeight;
1×
234
      ctx.putImageData(imageData, 0, 0);
1×
235
    }
236
    setTextStyle(ctx, fontFamily, fontSize, fontWeight);
1×
237

238
    // 3. layout characters
239
    if (sdf) {
Branches [[15, 0]] missed. 1×
UNCOV
240
      const tinySDF = new TinySDF(fontSize, buffer, radius, cutoff, fontFamily, fontWeight);
!
241
      // used to store distance values from tinySDF
242
      // tinySDF.size equals `fontSize + buffer * 2`
UNCOV
243
      const imageData = ctx.getImageData(0, 0, tinySDF.size, tinySDF.size);
!
244

UNCOV
245
      for (const char of characterSet) {
!
UNCOV
246
        populateAlphaChannel(tinySDF.draw(char), imageData);
!
UNCOV
247
        ctx.putImageData(imageData, mapping[char].x - buffer, mapping[char].y - buffer);
!
248
      }
249
    } else {
250
      for (const char of characterSet) {
1×
251
        ctx.fillText(char, mapping[char].x, mapping[char].y + fontSize * BASELINE_SCALE);
96×
252
      }
253
    }
254

255
    return {
1×
256
      xOffset,
257
      yOffset,
258
      mapping,
259
      data: canvas,
260
      width: canvas.width,
261
      height: canvas.height
262
    };
263
  }
264

265
  _getKey() {
266
    const {gl, fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff} = this.props;
4×
267
    if (sdf) {
Branches [[16, 0]] missed. 4×
UNCOV
268
      return `${gl} ${fontFamily} ${fontWeight} ${fontSize} ${buffer} ${radius} ${cutoff}`;
!
269
    }
270
    return `${gl} ${fontFamily} ${fontWeight} ${fontSize} ${buffer}`;
4×
271
  }
272
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC