diff --git a/CHANGELOG.md b/CHANGELOG.md index b3da31c264..84fa02adca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Fix 3D map freezing when camera is adjusted against map bounds. ([#4537](https://github.com/maplibre/maplibre-gl-js/issues/4537)) - Fix `getStyle()` to return a clone so the object cannot be internally changed ([#4488](https://github.com/maplibre/maplibre-gl-js/issues/4488)) - Prefer local glyph rendering for all CJKV characters, not just those in the CJK Unified Ideographs, Hiragana, Katakana, and Hangul Syllables blocks. ([#4560](https://github.com/maplibre/maplibre-gl-js/pull/4560))) +- Fix crash on missing glyph PBF. - - _...Add new stuff here..._ ## 4.5.2 diff --git a/src/style/load_glyph_range.test.ts b/src/style/load_glyph_range.test.ts index f79334a301..e0c1bc6244 100644 --- a/src/style/load_glyph_range.test.ts +++ b/src/style/load_glyph_range.test.ts @@ -5,7 +5,7 @@ import {loadGlyphRange} from './load_glyph_range'; import {fakeServer} from 'nise'; import {bufferToArrayBuffer} from '../util/test/util'; -test('loadGlyphRange', async () => { +describe('loadGlyphRange', () => { global.fetch = null; const transform = jest.fn().mockImplementation((url) => { @@ -14,27 +14,52 @@ test('loadGlyphRange', async () => { const manager = new RequestManager(transform); - const server = fakeServer.create(); - server.respondWith(bufferToArrayBuffer(fs.readFileSync(path.join(__dirname, '../../test/unit/assets/0-255.pbf')))); - - const promise = loadGlyphRange('Arial Unicode MS', 0, 'https://localhost/fonts/v1/{fontstack}/{range}.pbf', manager); - server.respond(); - const result = await promise; - - expect(transform).toHaveBeenCalledTimes(1); - expect(transform).toHaveBeenCalledWith('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf', 'Glyphs'); - - expect(Object.keys(result)).toHaveLength(223); - for (const key in result) { - const id = Number(key); - const glyph = result[id]; - - expect(glyph.id).toBe(Number(id)); - expect(glyph.metrics).toBeTruthy(); - expect(typeof glyph.metrics.width).toBe('number'); - expect(typeof glyph.metrics.height).toBe('number'); - expect(typeof glyph.metrics.top).toBe('number'); - expect(typeof glyph.metrics.advance).toBe('number'); - } - expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf'); + afterEach(() => { + jest.clearAllMocks(); + }); + + test('requests and receives a glyph range', async () => { + const server = fakeServer.create(); + server.respondWith(bufferToArrayBuffer(fs.readFileSync(path.join(__dirname, '../../test/unit/assets/0-255.pbf')))); + + const promise = loadGlyphRange('Arial Unicode MS', 0, 'https://localhost/fonts/v1/{fontstack}/{range}.pbf', manager); + server.respond(); + const result = await promise; + + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf', 'Glyphs'); + + expect(Object.keys(result)).toHaveLength(223); + for (const key in result) { + const id = Number(key); + const glyph = result[id]; + + expect(glyph.id).toBe(Number(id)); + expect(glyph.metrics).toBeTruthy(); + expect(typeof glyph.metrics.width).toBe('number'); + expect(typeof glyph.metrics.height).toBe('number'); + expect(typeof glyph.metrics.top).toBe('number'); + expect(typeof glyph.metrics.advance).toBe('number'); + } + expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf'); + }); + + test('warns on missing glyph range', async () => { + jest.spyOn(console, 'warn').mockImplementation(() => { }); + + const server = fakeServer.create(); + + const promise = loadGlyphRange('Arial Unicode MS', 2, 'https://localhost/fonts/v1/{fontstack}/{range}.pbf', manager); + server.respond(); + expect(async () => { + const result = await promise; + expect(console.warn).toHaveBeenCalledTimes(1); + expect(Object.keys(result)).toHaveLength(0); + }).not.toThrow(); + + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith('https://localhost/fonts/v1/Arial Unicode MS/512-767.pbf', 'Glyphs'); + + expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/512-767.pbf'); + }); }); diff --git a/src/style/load_glyph_range.ts b/src/style/load_glyph_range.ts index ba63c10a4b..32203c3533 100644 --- a/src/style/load_glyph_range.ts +++ b/src/style/load_glyph_range.ts @@ -18,14 +18,18 @@ export async function loadGlyphRange(fontstack: string, ResourceType.Glyphs ); - const response = await getArrayBuffer(request, new AbortController()); - if (!response || !response.data) { - throw new Error(`Could not load glyph range. range: ${range}, ${begin}-${end}`); - } const glyphs = {}; + try { + const response = await getArrayBuffer(request, new AbortController()); + if (!response || !response.data) { + throw new Error(`Could not load glyph range. range: ${range}, ${begin}-${end}`); + } - for (const glyph of parseGlyphPbf(response.data)) { - glyphs[glyph.id] = glyph; + for (const glyph of parseGlyphPbf(response.data)) { + glyphs[glyph.id] = glyph; + } + } catch (e) { + console.warn(`Could not load glyph range. range: ${range}, ${begin}-${end}`, e); } return glyphs; diff --git a/test/integration/render/tests/text-local-ideographs/cjk/expected-mac.png b/test/integration/render/tests/text-local-glyphs/cjk/expected-mac.png similarity index 100% rename from test/integration/render/tests/text-local-ideographs/cjk/expected-mac.png rename to test/integration/render/tests/text-local-glyphs/cjk/expected-mac.png diff --git a/test/integration/render/tests/text-local-ideographs/cjk/expected-ubuntu.png b/test/integration/render/tests/text-local-glyphs/cjk/expected-ubuntu.png similarity index 100% rename from test/integration/render/tests/text-local-ideographs/cjk/expected-ubuntu.png rename to test/integration/render/tests/text-local-glyphs/cjk/expected-ubuntu.png diff --git a/test/integration/render/tests/text-local-ideographs/cjk/expected-windows.png b/test/integration/render/tests/text-local-glyphs/cjk/expected-windows.png similarity index 100% rename from test/integration/render/tests/text-local-ideographs/cjk/expected-windows.png rename to test/integration/render/tests/text-local-glyphs/cjk/expected-windows.png diff --git a/test/integration/render/tests/text-local-ideographs/cjk/style.json b/test/integration/render/tests/text-local-glyphs/cjk/style.json similarity index 100% rename from test/integration/render/tests/text-local-ideographs/cjk/style.json rename to test/integration/render/tests/text-local-glyphs/cjk/style.json diff --git a/test/integration/render/tests/text-local-glyphs/missing/expected-mac.png b/test/integration/render/tests/text-local-glyphs/missing/expected-mac.png new file mode 100644 index 0000000000..42cb9d520d Binary files /dev/null and b/test/integration/render/tests/text-local-glyphs/missing/expected-mac.png differ diff --git a/test/integration/render/tests/text-local-glyphs/missing/expected-ubuntu.png b/test/integration/render/tests/text-local-glyphs/missing/expected-ubuntu.png new file mode 100644 index 0000000000..42cb9d520d Binary files /dev/null and b/test/integration/render/tests/text-local-glyphs/missing/expected-ubuntu.png differ diff --git a/test/integration/render/tests/text-local-glyphs/missing/expected-windows.png b/test/integration/render/tests/text-local-glyphs/missing/expected-windows.png new file mode 100644 index 0000000000..42cb9d520d Binary files /dev/null and b/test/integration/render/tests/text-local-glyphs/missing/expected-windows.png differ diff --git a/test/integration/render/tests/text-local-glyphs/missing/style.json b/test/integration/render/tests/text-local-glyphs/missing/style.json new file mode 100644 index 0000000000..99081eaca4 --- /dev/null +++ b/test/integration/render/tests/text-local-glyphs/missing/style.json @@ -0,0 +1,43 @@ +{ + "version": 8, + "metadata": { + "test": { + "pixelRatio": 2, + "localIdeographFontFamily": "sans-serif", + "width": 800, + "height": 600 + } + }, + "zoom": 8, + "sources": { + "sample": { + "type": "geojson", + "data": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "name_en": "Georgia", + "name_ka": "საქართველო" + } + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "sample-text", + "type": "symbol", + "source": "sample", + "layout": { + "text-anchor": "top", + "text-field": "{name_en} ({name_ka})", + "text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"], + "text-size": 30, + "text-offset": [0, 0] + } + } + ] +}