From b840832831b0bc7c74cef3ede1cc1d07db5830de Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 30 Oct 2023 22:09:36 -0400 Subject: [PATCH 01/30] Re-render after font load --- shieldlib/src/shield_renderer.ts | 35 ++++++++++++++++++++++++++++---- src/bare_map.html | 7 ------- src/index.html | 9 -------- src/shieldtest.html | 9 -------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 9194feb32..4e95ed12e 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -1,4 +1,8 @@ -import { Map, MapStyleImageMissingEvent, StyleImage } from "maplibre-gl"; +import { + Map as MapLibre, + MapStyleImageMissingEvent, + StyleImage, +} from "maplibre-gl"; import { Bounds, DebugOptions, @@ -52,8 +56,8 @@ export type ShapeDrawFunction = ( ) => number; class MaplibreGLSpriteRepository implements SpriteRepository { - map: Map; - constructor(map: Map) { + map: MapLibre; + constructor(map: MapLibre) { this.map = map; } getSprite(spriteID: string): StyleImage { @@ -69,6 +73,10 @@ export class AbstractShieldRenderer { private _shieldPredicate: StringPredicate = () => true; private _networkPredicate: StringPredicate = () => true; private _routeParser: RouteParser; + private _fontsLoaded: boolean = false; + /** Cache images that are loaded before fonts so they can be re-rendered later */ + private _preFontImageCache: Map = new Map(); + /** @hidden */ private _renderContext: ShieldRenderingContext; private _shieldDefCallbacks = []; @@ -123,9 +131,25 @@ export class AbstractShieldRenderer { } /** Set which MaplibreGL map to handle shields for */ - public renderOnMaplibreGL(map: Map): AbstractShieldRenderer { + public renderOnMaplibreGL(map: MapLibre): AbstractShieldRenderer { this.renderOnRepository(new MaplibreGLSpriteRepository(map)); map.on("styleimagemissing", this.getStyleImageMissingHandler()); + document.fonts.ready.then(() => { + this._fontsLoaded = true; + if (this._preFontImageCache.size == 0) { + return; + } + console.log("Re-processing shields with loaded fonts"); + + // Loop through each previously-loaded shield and re-render it + for (let [id, routeDef] of this._preFontImageCache.entries()) { + map.removeImage(id); + missingIconLoader(this._renderContext, routeDef, id); + console.log(`Updated ${id} post font-load`); // Example action + } + + this._preFontImageCache.clear(); + }); return this; } @@ -166,6 +190,9 @@ export class AbstractShieldRenderer { return; } if (routeDef) { + if (!this._fontsLoaded && routeDef.ref) { + this._preFontImageCache.set(e.id, routeDef); + } missingIconLoader(this._renderContext, routeDef, e.id); } } catch (err) { diff --git a/src/bare_map.html b/src/bare_map.html index 0aa072f6b..4be225275 100644 --- a/src/bare_map.html +++ b/src/bare_map.html @@ -27,13 +27,6 @@ -

- Invisible text so the font will load early -

diff --git a/src/index.html b/src/index.html index 89a840fd2..fff7f541d 100644 --- a/src/index.html +++ b/src/index.html @@ -98,15 +98,6 @@ -

- Invisible text so the font will load early -

-

- Invisible text so the font will load early -

From 363af8c05b1d34dcd99843d807fa85d89296fa32 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Mon, 30 Oct 2023 23:22:49 -0400 Subject: [PATCH 02/30] Refactor SpriteRepository for updateImage() --- shieldlib/src/shield.d.ts | 3 ++- shieldlib/src/shield_renderer.ts | 14 +++++++++++--- shieldlib/src/types.ts | 7 ++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/shieldlib/src/shield.d.ts b/shieldlib/src/shield.d.ts index fb44bf80b..38e055424 100644 --- a/shieldlib/src/shield.d.ts +++ b/shieldlib/src/shield.d.ts @@ -15,7 +15,8 @@ export function storeNoShield( export function missingIconLoader( renderContext: ShieldRenderingContext, routeDef: RouteDefinition, - spriteID: string + spriteID: string, + update?: boolean ): void; export function romanizeRef(ref: string): string; diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 4e95ed12e..1eb4996e4 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -63,8 +63,17 @@ class MaplibreGLSpriteRepository implements SpriteRepository { getSprite(spriteID: string): StyleImage { return this.map.style.getImage(spriteID); } - putSprite(spriteID: string, image: ImageData, pixelRatio: number): void { - this.map.addImage(spriteID, image, { pixelRatio: pixelRatio }); + putSprite( + spriteID: string, + image: ImageData, + pixelRatio: number, + update?: boolean + ): void { + if (update) { + this.map.updateImage(spriteID, image); + } else { + this.map.addImage(spriteID, image, { pixelRatio: pixelRatio }); + } } } @@ -143,7 +152,6 @@ export class AbstractShieldRenderer { // Loop through each previously-loaded shield and re-render it for (let [id, routeDef] of this._preFontImageCache.entries()) { - map.removeImage(id); missingIconLoader(this._renderContext, routeDef, id); console.log(`Updated ${id} post font-load`); // Example action } diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 63ae4ff1d..7c57e8ede 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -135,7 +135,12 @@ export interface SpriteProducer { /** Store a sprite graphic based on an ID */ export interface SpriteConsumer { - putSprite(spriteID: string, image: ImageData, pixelRatio: number): void; + putSprite( + spriteID: string, + image: ImageData, + pixelRatio: number, + update?: boolean + ): void; } /** Respository that can store and retrieve sprite graphics */ From ea05b394b4dd1e59f94623f3f413d6ac2a85caa5 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 1 Nov 2023 11:46:09 -0400 Subject: [PATCH 03/30] Update shieldlib/src/shield_renderer.ts Co-authored-by: Josh Lee --- shieldlib/src/shield_renderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 1eb4996e4..453c09ece 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -157,6 +157,7 @@ export class AbstractShieldRenderer { } this._preFontImageCache.clear(); + map.redraw(); }); return this; } From 1344f63b8350b1da64bc3959ff4c0c1bc3d59e42 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 1 Nov 2023 19:05:29 -0400 Subject: [PATCH 04/30] Add missing update param --- shieldlib/src/shield_renderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 453c09ece..5cfb759a5 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -152,7 +152,7 @@ export class AbstractShieldRenderer { // Loop through each previously-loaded shield and re-render it for (let [id, routeDef] of this._preFontImageCache.entries()) { - missingIconLoader(this._renderContext, routeDef, id); + missingIconLoader(this._renderContext, routeDef, id, true); console.log(`Updated ${id} post font-load`); // Example action } @@ -202,7 +202,7 @@ export class AbstractShieldRenderer { if (!this._fontsLoaded && routeDef.ref) { this._preFontImageCache.set(e.id, routeDef); } - missingIconLoader(this._renderContext, routeDef, e.id); + missingIconLoader(this._renderContext, routeDef, e.id, false); } } catch (err) { console.error(`Exception while loading image ‘${e?.id}’:\n`, err); From 2c353b3ac2511976efe9823f65726742ac681712 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Wed, 1 Nov 2023 20:40:19 -0400 Subject: [PATCH 05/30] Add code path through shield.js --- shieldlib/src/shield.js | 9 +++++---- shieldlib/src/shield_renderer.ts | 4 +++- shieldlib/src/types.d.ts | 7 ++++++- shieldlib/src/types.ts | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/shieldlib/src/shield.js b/shieldlib/src/shield.js index dd15c53d0..a4fac4496 100644 --- a/shieldlib/src/shield.js +++ b/shieldlib/src/shield.js @@ -166,17 +166,17 @@ function drawShieldText(r, ctx, shieldDef, routeDef) { return ctx; } -export function missingIconLoader(r, routeDef, spriteID) { +export function missingIconLoader(r, routeDef, spriteID, update) { let ctx = generateShieldCtx(r, routeDef); if (ctx == null) { // Want to return null here, but that gives a corrupted display. See #243 console.warn("Didn't produce a shield for", JSON.stringify(routeDef)); ctx = r.gfxFactory.createGraphics({ width: 1, height: 1 }); } - storeSprite(r, spriteID, ctx); + storeSprite(r, spriteID, ctx, update); } -function storeSprite(r, id, ctx) { +function storeSprite(r, id, ctx, update) { const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); r.spriteRepo.putSprite( id, @@ -185,7 +185,8 @@ function storeSprite(r, id, ctx) { height: ctx.canvas.height, data: imgData.data, }, - r.px(1) + r.px(1), + update ); } diff --git a/shieldlib/src/shield_renderer.ts b/shieldlib/src/shield_renderer.ts index 5cfb759a5..be0f44549 100644 --- a/shieldlib/src/shield_renderer.ts +++ b/shieldlib/src/shield_renderer.ts @@ -67,11 +67,13 @@ class MaplibreGLSpriteRepository implements SpriteRepository { spriteID: string, image: ImageData, pixelRatio: number, - update?: boolean + update: boolean ): void { if (update) { + console.log(`update ${spriteID}`); this.map.updateImage(spriteID, image); } else { + console.log(`add ${spriteID}`); this.map.addImage(spriteID, image, { pixelRatio: pixelRatio }); } } diff --git a/shieldlib/src/types.d.ts b/shieldlib/src/types.d.ts index a81c725ad..cb78e4ad2 100644 --- a/shieldlib/src/types.d.ts +++ b/shieldlib/src/types.d.ts @@ -23,7 +23,12 @@ export interface SpriteProducer { getSprite(spriteID: string): StyleImage; } export interface SpriteConsumer { - putSprite(spriteID: string, image: ImageData, pixelRatio: number): void; + putSprite( + spriteID: string, + image: ImageData, + pixelRatio: number, + update: boolean + ): void; } export type SpriteRepository = SpriteProducer & SpriteConsumer; export interface ShieldDefinitions { diff --git a/shieldlib/src/types.ts b/shieldlib/src/types.ts index 7c57e8ede..e4063e486 100644 --- a/shieldlib/src/types.ts +++ b/shieldlib/src/types.ts @@ -139,7 +139,7 @@ export interface SpriteConsumer { spriteID: string, image: ImageData, pixelRatio: number, - update?: boolean + update: boolean ): void; } From f5a6632cf27dc1bbd094e6553f34cddf005f95f7 Mon Sep 17 00:00:00 2001 From: Brian Sperlongano Date: Fri, 3 Nov 2023 23:40:11 -0400 Subject: [PATCH 06/30] Working font loader --- scripts/build.ts | 1 + scripts/generate_samples.ts | 20 ++++++++++++++++---- src/bare_map.html | 1 + src/fonts.css | 2 ++ src/index.html | 1 + src/js/load_fonts.js | 26 ++++++++++++++++++++++++++ src/shieldtest.html | 1 + 7 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/js/load_fonts.js diff --git a/scripts/build.ts b/scripts/build.ts index 1ba418418..0a466a549 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -41,6 +41,7 @@ const buildWith = async ( "src/americana.js", "src/bare_americana.js", "src/shieldtest.js", + "src/js/load_fonts.js", ], format: "esm", bundle: true, diff --git a/scripts/generate_samples.ts b/scripts/generate_samples.ts index 62569533a..29a4a530e 100644 --- a/scripts/generate_samples.ts +++ b/scripts/generate_samples.ts @@ -4,7 +4,7 @@ import type * as maplibre from "maplibre-gl"; // Declare a global augmentation for the Window interface declare global { - interface Window { + interface WindowWithMap extends Window { map?: maplibre.Map; } } @@ -43,10 +43,14 @@ const screenshots: SampleSpecification[] = fs.mkdirSync(sampleFolder, { recursive: true }); const browser = await chromium.launch({ - headless: true, + headless: false, executablePath: process.env.CHROME_BIN, }); -const context = await browser.newContext(); +const context = await browser.newContext({ + bypassCSP: true, + userAgent: + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36", +}); const page = await context.newPage(); @@ -62,9 +66,17 @@ async function createImage(screenshot: SampleSpecification) { `http://localhost:1776/${pagePath}#map=${screenshot.location}` ); + // 3. Wait until all fonts are loaded + await page.waitForFunction(() => { + const fontFaceSet: FontFaceSet = document.fonts; + return Array.from(fontFaceSet.values()).every( + (font) => font.status !== "unloaded" + ); + }); + // Wait for map to load, then wait two more seconds for images, etc. to load. try { - await page.waitForFunction(() => window.map?.loaded(), { + await page.waitForFunction(() => (window as WindowWithMap).map?.loaded(), { timeout: 3000, }); } catch (e) { diff --git a/src/bare_map.html b/src/bare_map.html index 4be225275..30d291ac3 100644 --- a/src/bare_map.html +++ b/src/bare_map.html @@ -5,6 +5,7 @@ OpenStreetMap Americana + OpenStreetMap Americana + Shield Test +