package.src.render.glyph_manager.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mapbox-gl Show documentation
Show all versions of mapbox-gl Show documentation
A WebGL interactive maps library
The newest version!
// @flow
import loadGlyphRange from '../style/load_glyph_range';
import TinySDF from '@mapbox/tiny-sdf';
import isChar from '../util/is_char_in_unicode_block';
import {asyncAll} from '../util/util';
import {AlphaImage} from '../util/image';
import type {StyleGlyph} from '../style/style_glyph';
import type {RequestManager} from '../util/mapbox';
import type {Callback} from '../types/callback';
type Entry = {
// null means we've requested the range, but the glyph wasn't included in the result.
glyphs: {[id: number]: StyleGlyph | null},
requests: {[range: number]: Array>},
ranges: {[range: number]: boolean | null},
tinySDF?: TinySDF
};
class GlyphManager {
requestManager: RequestManager;
localIdeographFontFamily: ?string;
entries: {[_: string]: Entry};
url: ?string;
// exposed as statics to enable stubbing in unit tests
static loadGlyphRange: typeof loadGlyphRange;
static TinySDF: Class;
constructor(requestManager: RequestManager, localIdeographFontFamily: ?string) {
this.requestManager = requestManager;
this.localIdeographFontFamily = localIdeographFontFamily;
this.entries = {};
}
setURL(url: ?string) {
this.url = url;
}
getGlyphs(glyphs: {[stack: string]: Array}, callback: Callback<{[stack: string]: {[id: number]: ?StyleGlyph}}>) {
const all = [];
for (const stack in glyphs) {
for (const id of glyphs[stack]) {
all.push({stack, id});
}
}
asyncAll(all, ({stack, id}, callback: Callback<{stack: string, id: number, glyph: ?StyleGlyph}>) => {
let entry = this.entries[stack];
if (!entry) {
entry = this.entries[stack] = {
glyphs: {},
requests: {},
ranges: {}
};
}
let glyph = entry.glyphs[id];
if (glyph !== undefined) {
callback(null, {stack, id, glyph});
return;
}
glyph = this._tinySDF(entry, stack, id);
if (glyph) {
entry.glyphs[id] = glyph;
callback(null, {stack, id, glyph});
return;
}
const range = Math.floor(id / 256);
if (range * 256 > 65535) {
callback(new Error('glyphs > 65535 not supported'));
return;
}
if (entry.ranges[range]) {
callback(null, {stack, id, glyph});
return;
}
let requests = entry.requests[range];
if (!requests) {
requests = entry.requests[range] = [];
GlyphManager.loadGlyphRange(stack, range, (this.url: any), this.requestManager,
(err, response: ?{[_: number]: StyleGlyph | null}) => {
if (response) {
for (const id in response) {
if (!this._doesCharSupportLocalGlyph(+id)) {
entry.glyphs[+id] = response[+id];
}
}
entry.ranges[range] = true;
}
for (const cb of requests) {
cb(err, response);
}
delete entry.requests[range];
});
}
requests.push((err, result: ?{[_: number]: StyleGlyph | null}) => {
if (err) {
callback(err);
} else if (result) {
callback(null, {stack, id, glyph: result[id] || null});
}
});
}, (err, glyphs: ?Array<{stack: string, id: number, glyph: ?StyleGlyph}>) => {
if (err) {
callback(err);
} else if (glyphs) {
const result = {};
for (const {stack, id, glyph} of glyphs) {
// Clone the glyph so that our own copy of its ArrayBuffer doesn't get transferred.
(result[stack] || (result[stack] = {}))[id] = glyph && {
id: glyph.id,
bitmap: glyph.bitmap.clone(),
metrics: glyph.metrics
};
}
callback(null, result);
}
});
}
_doesCharSupportLocalGlyph(id: number): boolean {
/* eslint-disable new-cap */
return !!this.localIdeographFontFamily &&
(isChar['CJK Unified Ideographs'](id) ||
isChar['Hangul Syllables'](id) ||
isChar['Hiragana'](id) ||
isChar['Katakana'](id));
/* eslint-enable new-cap */
}
_tinySDF(entry: Entry, stack: string, id: number): ?StyleGlyph {
const family = this.localIdeographFontFamily;
if (!family) {
return;
}
if (!this._doesCharSupportLocalGlyph(id)) {
return;
}
let tinySDF = entry.tinySDF;
if (!tinySDF) {
let fontWeight = '400';
if (/bold/i.test(stack)) {
fontWeight = '900';
} else if (/medium/i.test(stack)) {
fontWeight = '500';
} else if (/light/i.test(stack)) {
fontWeight = '200';
}
tinySDF = entry.tinySDF = new GlyphManager.TinySDF(24, 3, 8, .25, family, fontWeight);
}
return {
id,
bitmap: new AlphaImage({width: 30, height: 30}, tinySDF.draw(String.fromCharCode(id))),
metrics: {
width: 24,
height: 24,
left: 0,
top: -8,
advance: 24
}
};
}
}
GlyphManager.loadGlyphRange = loadGlyphRange;
GlyphManager.TinySDF = TinySDF;
export default GlyphManager;