diff --git a/src/render/glyph_manager.js b/src/render/glyph_manager.js index 940f9130d30..2b743f1b791 100644 --- a/src/render/glyph_manager.js +++ b/src/render/glyph_manager.js @@ -59,13 +59,14 @@ class GlyphManager { // Multiple fontstacks may share the same local glyphs, so keep an index // into the glyphs based soley on font weight localGlyphs: {[_: string]: {glyphs: {[id: number]: StyleGlyph | null}, ascender: ?number, descender: ?number}}; + enableFallbackGlyph: ?boolean urls: {[scope: string]: ?string}; // exposed as statics to enable stubbing in unit tests static loadGlyphRange: typeof loadGlyphRange; static TinySDF: Class; - constructor(requestManager: RequestManager, localGlyphMode: number, localFontFamily: ?string) { + constructor(requestManager: RequestManager, localGlyphMode: number, localFontFamily: ?string, enableFallbackGlyph: ?boolean) { this.requestManager = requestManager; this.localGlyphMode = localGlyphMode; this.localFontFamily = localFontFamily; @@ -78,6 +79,7 @@ class GlyphManager { '500': {}, '900': {} }; + this.enableFallbackGlyph = enableFallbackGlyph; } setURL(url: ?string, scope: string) { @@ -91,8 +93,13 @@ class GlyphManager { const url = this.urls[scope] || config.GLYPHS_URL; for (const stack in glyphs) { + let doesCharSupportFallbackGlyphRange = false; for (const id of glyphs[stack]) { all.push({stack, id}); + if (this.localGlyphMode !== LocalGlyphMode.all && !!this.enableFallbackGlyph && id >= 0 && id <= 255) doesCharSupportFallbackGlyphRange = true; + } + if (this.localGlyphMode !== LocalGlyphMode.all && !!this.enableFallbackGlyph && !doesCharSupportFallbackGlyphRange) { + all.push({stack, id: 63}); } } @@ -171,15 +178,29 @@ class GlyphManager { // Clone the glyph so that our own copy of its ArrayBuffer doesn't get transferred. if (result[stack] === undefined) result[stack] = {}; if (result[stack].glyphs === undefined) result[stack].glyphs = {}; - result[stack].glyphs[id] = glyph && { - id: glyph.id, - bitmap: glyph.bitmap.clone(), - metrics: glyph.metrics - }; + if (glyph) { + result[stack].glyphs[id] = { + id: glyph.id, + bitmap: glyph.bitmap.clone(), + metrics: glyph.metrics, + }; + } else if (this.enableFallbackGlyph) { + const fallbackGlyph = this.entries[stack].glyphs[63]; + if (fallbackGlyph) { + result[stack].glyphs[id] = { + id, + bitmap: fallbackGlyph.bitmap.clone(), + metrics: fallbackGlyph.metrics, + }; + } else { + result[stack].glyphs[id] = null; + } + } else { + result[stack].glyphs[id] = null; + } result[stack].ascender = this.entries[stack].ascender; result[stack].descender = this.entries[stack].descender; } - callback(null, result); } }); diff --git a/src/style/style.js b/src/style/style.js index bd9f6050904..908f96d5daf 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -148,6 +148,7 @@ export type StyleOptions = { validate?: boolean, localFontFamily?: ?string, localIdeographFontFamily?: string, + enableFallbackGlyph?: boolean, dispatcher?: Dispatcher, imageManager?: ImageManager, @@ -306,7 +307,7 @@ class Style extends Evented { options.localFontFamily ? LocalGlyphMode.all : (options.localIdeographFontFamily ? LocalGlyphMode.ideographs : LocalGlyphMode.none), - options.localFontFamily || options.localIdeographFontFamily); + options.localFontFamily || options.localIdeographFontFamily, options.enableFallbackGlyph); } if (options.modelManager) { diff --git a/src/ui/map.js b/src/ui/map.js index 79609ff5d14..e8f2ccc8526 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -195,6 +195,7 @@ const defaultOptions = { maxTileCacheSize: null, localIdeographFontFamily: 'sans-serif', localFontFamily: null, + enableFallbackGlyph: false, transformRequest: null, accessToken: null, fadeDuration: 300, @@ -301,6 +302,7 @@ const defaultOptions = { * @param {string} [options.localFontFamily=null] Defines a CSS * font-family for locally overriding generation of all glyphs. Font settings from the map's style will be ignored, except for font-weight keywords (light/regular/medium/bold). * If set, this option overrides the setting in localIdeographFontFamily. + * @param {boolean} [options.enableFallbackGlyph=false] If `true`, will show the fallback character ? In place of characters which are missing from custom fonts. * @param {RequestTransformFunction} [options.transformRequest=null] A callback run before the Map makes a request for an external URL. The callback can be used to modify the url, set headers, or set the credentials property for cross-origin requests. * Expected to return a {@link RequestParameters} object with a `url` property and optionally `headers` and `credentials` properties. * @param {boolean} [options.collectResourceTiming=false] If `true`, Resource Timing API information will be collected for requests made by GeoJSON and Vector Tile web workers (this information is normally inaccessible from the main Javascript thread). Information will be returned in a `resourceTiming` property of relevant `data` events. @@ -400,6 +402,7 @@ class Map extends Camera { _mapId: number; _localIdeographFontFamily: string; _localFontFamily: ?string; + _enableFallbackGlyph: ?boolean; _requestManager: RequestManager; _locale: Object; _removed: boolean; @@ -611,10 +614,11 @@ class Map extends Camera { this._localFontFamily = options.localFontFamily; this._localIdeographFontFamily = options.localIdeographFontFamily; + this._enableFallbackGlyph = options.enableFallbackGlyph; if (options.style || !options.testMode) { const style = options.style || config.DEFAULT_STYLE; - this.setStyle(style, {localFontFamily: this._localFontFamily, localIdeographFontFamily: this._localIdeographFontFamily}); + this.setStyle(style, {localFontFamily: this._localFontFamily, localIdeographFontFamily: this._localIdeographFontFamily, enableFallbackGlyph: this._enableFallbackGlyph}); } if (options.projection) { @@ -1940,16 +1944,17 @@ class Map extends Camera { * @see [Example: Change a map's style](https://www.mapbox.com/mapbox-gl-js/example/setstyle/) */ setStyle(style: StyleSpecification | string | null, options?: {diff?: boolean} & StyleOptions): this { - options = extend({}, {localIdeographFontFamily: this._localIdeographFontFamily, localFontFamily: this._localFontFamily}, options); + options = extend({}, {localIdeographFontFamily: this._localIdeographFontFamily, localFontFamily: this._localFontFamily, enableFallbackGlyph: this._enableFallbackGlyph}, options); if ((options.diff !== false && options.localIdeographFontFamily === this._localIdeographFontFamily && - options.localFontFamily === this._localFontFamily) && this.style && style) { + options.localFontFamily === this._localFontFamily && options.enableFallbackGlyph === this._enableFallbackGlyph) && this.style && style) { this._diffStyle(style, options); return this; } else { this._localIdeographFontFamily = options.localIdeographFontFamily; this._localFontFamily = options.localFontFamily; + this._enableFallbackGlyph = options.enableFallbackGlyph; return this._updateStyle(style, options); } }