UNPKG

mapbox-gl

Version:
207 lines (170 loc) 5.76 kB
'use strict'; var ShelfPack = require('shelf-pack'); module.exports = GlyphAtlas; function GlyphAtlas(width, height) { this.width = width; this.height = height; this.bin = new ShelfPack(width, height); this.index = {}; this.ids = {}; this.data = new Uint8Array(width * height); } GlyphAtlas.prototype = { get debug() { return 'canvas' in this; }, set debug(value) { if (value && !this.canvas) { this.canvas = document.createElement('canvas'); this.canvas.width = this.width; this.canvas.height = this.height; document.body.appendChild(this.canvas); this.ctx = this.canvas.getContext('2d'); } else if (!value && this.canvas) { this.canvas.parentNode.removeChild(this.canvas); delete this.ctx; delete this.canvas; } } }; GlyphAtlas.prototype.getGlyphs = function() { var glyphs = {}, split, name, id; for (var key in this.ids) { split = key.split('#'); name = split[0]; id = split[1]; if (!glyphs[name]) glyphs[name] = []; glyphs[name].push(id); } return glyphs; }; GlyphAtlas.prototype.getRects = function() { var rects = {}, split, name, id; for (var key in this.ids) { split = key.split('#'); name = split[0]; id = split[1]; if (!rects[name]) rects[name] = {}; rects[name][id] = this.index[key]; } return rects; }; GlyphAtlas.prototype.addGlyph = function(id, name, glyph, buffer) { if (!glyph) { // console.warn('missing glyph', code, String.fromCharCode(code)); return null; } var key = name + "#" + glyph.id; // The glyph is already in this texture. if (this.index[key]) { if (this.ids[key].indexOf(id) < 0) { this.ids[key].push(id); } return this.index[key]; } // The glyph bitmap has zero width. if (!glyph.bitmap) { return null; } var bufferedWidth = glyph.width + buffer * 2; var bufferedHeight = glyph.height + buffer * 2; // Add a 1px border around every image. var padding = 1; var packWidth = bufferedWidth + 2 * padding; var packHeight = bufferedHeight + 2 * padding; // Increase to next number divisible by 4, but at least 1. // This is so we can scale down the texture coordinates and pack them // into 2 bytes rather than 4 bytes. packWidth += (4 - packWidth % 4); packHeight += (4 - packHeight % 4); var rect = this.bin.allocate(packWidth, packHeight); if (rect.x < 0) { this.resize(); rect = this.bin.allocate(packWidth, packHeight); } if (rect.x < 0) { console.warn('glyph bitmap overflow'); return { glyph: glyph, rect: null }; } this.index[key] = rect; this.ids[key] = [id]; var target = this.data; var source = glyph.bitmap; for (var y = 0; y < bufferedHeight; y++) { var y1 = this.width * (rect.y + y + padding) + rect.x + padding; var y2 = bufferedWidth * y; for (var x = 0; x < bufferedWidth; x++) { target[y1 + x] = source[y2 + x]; } } this.dirty = true; return rect; }; GlyphAtlas.prototype.resize = function() { var origw = this.width, origh = this.height; // For now, don't grow the atlas beyond 1024x1024 because of how // texture coords pack into unsigned byte in symbol bucket. if (origw > 512 || origh > 512) return; if (this.texture) { if (this.gl) { this.gl.deleteTexture(this.texture); } this.texture = null; } this.width *= 2; this.height *= 2; this.bin.resize(this.width, this.height); var buf = new ArrayBuffer(this.width * this.height), src, dst; for (var i = 0; i < origh; i++) { src = new Uint8Array(this.data.buffer, origh * i, origw); dst = new Uint8Array(buf, origh * i * 2, origw); dst.set(src); } this.data = new Uint8Array(buf); }; GlyphAtlas.prototype.bind = function(gl) { this.gl = gl; if (!this.texture) { this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, this.width, this.height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, null); } else { gl.bindTexture(gl.TEXTURE_2D, this.texture); } }; GlyphAtlas.prototype.updateTexture = function(gl) { this.bind(gl); if (this.dirty) { gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, gl.ALPHA, gl.UNSIGNED_BYTE, this.data); // DEBUG if (this.ctx) { var data = this.ctx.getImageData(0, 0, this.width, this.height); for (var i = 0, j = 0; i < this.data.length; i++, j += 4) { data.data[j] = this.data[i]; data.data[j + 1] = this.data[i]; data.data[j + 2] = this.data[i]; data.data[j + 3] = 255; } this.ctx.putImageData(data, 0, 0); this.ctx.strokeStyle = 'red'; for (var k = 0; k < this.bin.free.length; k++) { var free = this.bin.free[k]; this.ctx.strokeRect(free.x, free.y, free.w, free.h); } } // END DEBUG this.dirty = false; } };