From ae25dd5c640b7890aae1fc87651dd4a65d9bde95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 15 Aug 2024 14:53:56 -0700 Subject: [PATCH] Warn but continue if requested glyph PBF is unavailable --- CHANGELOG.md | 1 + src/style/load_glyph_range.test.ts | 73 ++++++++++++------ src/style/load_glyph_range.ts | 16 ++-- .../cjk/expected-mac.png | Bin .../cjk/expected-ubuntu.png | Bin .../cjk/expected-windows.png | Bin .../cjk/style.json | 0 .../missing/expected-mac.png | Bin 0 -> 16074 bytes .../missing/expected-ubuntu.png | Bin 0 -> 16074 bytes .../missing/expected-windows.png | Bin 0 -> 16074 bytes .../text-local-glyphs/missing/style.json | 43 +++++++++++ 11 files changed, 103 insertions(+), 30 deletions(-) rename test/integration/render/tests/{text-local-ideographs => text-local-glyphs}/cjk/expected-mac.png (100%) rename test/integration/render/tests/{text-local-ideographs => text-local-glyphs}/cjk/expected-ubuntu.png (100%) rename test/integration/render/tests/{text-local-ideographs => text-local-glyphs}/cjk/expected-windows.png (100%) rename test/integration/render/tests/{text-local-ideographs => text-local-glyphs}/cjk/style.json (100%) create mode 100644 test/integration/render/tests/text-local-glyphs/missing/expected-mac.png create mode 100644 test/integration/render/tests/text-local-glyphs/missing/expected-ubuntu.png create mode 100644 test/integration/render/tests/text-local-glyphs/missing/expected-windows.png create mode 100644 test/integration/render/tests/text-local-glyphs/missing/style.json 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 0000000000000000000000000000000000000000..42cb9d520dea227d175137be52d41bf065c500a4 GIT binary patch literal 16074 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