UNPKG

mapbox-gl

Version:
141 lines (119 loc) 4.46 kB
'use strict'; var normalizeURL = require('../util/mapbox').normalizeGlyphsURL; var getArrayBuffer = require('../util/ajax').getArrayBuffer; var Glyphs = require('../util/glyphs'); var GlyphAtlas = require('../symbol/glyph_atlas'); var Protobuf = require('pbf'); module.exports = GlyphSource; /** * A glyph source has a URL from which to load new glyphs and manages * GlyphAtlases in which to store glyphs used by the requested fontstacks * and ranges. * * @param {string} url glyph template url * @private */ function GlyphSource(url) { this.url = url && normalizeURL(url); this.atlases = {}; this.stacks = {}; this.loading = {}; } GlyphSource.prototype.getSimpleGlyphs = function(fontstack, glyphIDs, uid, callback) { if (this.stacks[fontstack] === undefined) { this.stacks[fontstack] = {}; } if (this.atlases[fontstack] === undefined) { this.atlases[fontstack] = new GlyphAtlas(128, 128); } var glyphs = {}; var stack = this.stacks[fontstack]; var atlas = this.atlases[fontstack]; // the number of pixels the sdf bitmaps are padded by var buffer = 3; var missing = {}; var remaining = 0; var range; for (var i = 0; i < glyphIDs.length; i++) { var glyphID = glyphIDs[i]; range = Math.floor(glyphID / 256); if (stack[range]) { var glyph = stack[range].glyphs[glyphID]; var rect = atlas.addGlyph(uid, fontstack, glyph, buffer); if (glyph) glyphs[glyphID] = new SimpleGlyph(glyph, rect, buffer); } else { if (missing[range] === undefined) { missing[range] = []; remaining++; } missing[range].push(glyphID); } } if (!remaining) callback(undefined, glyphs, fontstack); var onRangeLoaded = function(err, range, data) { // TODO not be silent about errors if (!err) { var stack = this.stacks[fontstack][range] = data.stacks[0]; for (var i = 0; i < missing[range].length; i++) { var glyphID = missing[range][i]; var glyph = stack.glyphs[glyphID]; var rect = atlas.addGlyph(uid, fontstack, glyph, buffer); if (glyph) glyphs[glyphID] = new SimpleGlyph(glyph, rect, buffer); } } remaining--; if (!remaining) callback(undefined, glyphs, fontstack); }.bind(this); for (var r in missing) { this.loadRange(fontstack, r, onRangeLoaded); } }; // A simplified representation of the glyph containing only the properties needed for shaping. function SimpleGlyph(glyph, rect, buffer) { var padding = 1; this.advance = glyph.advance; this.left = glyph.left - buffer - padding; this.top = glyph.top + buffer + padding; this.rect = rect; } GlyphSource.prototype.loadRange = function(fontstack, range, callback) { if (range * 256 > 65535) return callback('glyphs > 65535 not supported'); if (this.loading[fontstack] === undefined) { this.loading[fontstack] = {}; } var loading = this.loading[fontstack]; if (loading[range]) { loading[range].push(callback); } else { loading[range] = [callback]; var rangeName = (range * 256) + '-' + (range * 256 + 255); var url = glyphUrl(fontstack, rangeName, this.url); getArrayBuffer(url, function(err, data) { var glyphs = !err && new Glyphs(new Protobuf(new Uint8Array(data))); for (var i = 0; i < loading[range].length; i++) { loading[range][i](err, range, glyphs); } delete loading[range]; }); } }; GlyphSource.prototype.getGlyphAtlas = function(fontstack) { return this.atlases[fontstack]; }; /** * Use CNAME sharding to load a specific glyph range over a randomized * but consistent subdomain. * @param {string} fontstack comma-joined fonts * @param {string} range comma-joined range * @param {url} url templated url * @param {string} [subdomains=abc] subdomains as a string where each letter is one. * @returns {string} a url to load that section of glyphs * @private */ function glyphUrl(fontstack, range, url, subdomains) { subdomains = subdomains || 'abc'; return url .replace('{s}', subdomains[fontstack.length % subdomains.length]) .replace('{fontstack}', fontstack) .replace('{range}', range); }