diff --git a/scripts/generate_samples.ts b/scripts/generate_samples.ts index 62569533a..3093bba0f 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, executablePath: process.env.CHROME_BIN, + args: ["--headless=new"], +}); +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 context = await browser.newContext(); const page = await context.newPage(); @@ -64,7 +68,7 @@ async function createImage(screenshot: SampleSpecification) { // 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/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.js b/shieldlib/src/shield.js index 6d8798e61..698cc254b 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 9194feb32..b01f40251 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,15 +56,25 @@ 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 { 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.removeImage(spriteID); + this.map.addImage(spriteID, image); + } else { + this.map.addImage(spriteID, image, { pixelRatio: pixelRatio }); + } } } @@ -69,6 +83,12 @@ export class AbstractShieldRenderer { private _shieldPredicate: StringPredicate = () => true; private _networkPredicate: StringPredicate = () => true; private _routeParser: RouteParser; + private _fontSpec: string; + private _map: MapLibre; + 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 = []; @@ -84,11 +104,31 @@ export class AbstractShieldRenderer { protected setShields(shieldSpec: ShieldSpecification) { this._renderContext.options = shieldSpec.options; this._renderContext.shieldDef = shieldSpec.networks; + this._fontSpec = "1em " + shieldSpec.options.shieldFont; + if (this._map) { + this.reloadShieldsOnFontLoad(); + } this._shieldDefCallbacks.forEach((callback) => callback(shieldSpec.networks) ); } + private onFontsLoaded() { + 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()) { + missingIconLoader(this._renderContext, routeDef, id, true); + } + + this._preFontImageCache.clear(); + this._map.redraw(); + } + /** Get the shield definitions */ public getShieldDefinitions(): ShieldDefinitions { return this._renderContext.shieldDef; @@ -123,12 +163,24 @@ export class AbstractShieldRenderer { } /** Set which MaplibreGL map to handle shields for */ - public renderOnMaplibreGL(map: Map): AbstractShieldRenderer { + public renderOnMaplibreGL(map: MapLibre): AbstractShieldRenderer { + this._map = map; + if (this._fontSpec) { + this.reloadShieldsOnFontLoad(); + } this.renderOnRepository(new MaplibreGLSpriteRepository(map)); map.on("styleimagemissing", this.getStyleImageMissingHandler()); return this; } + private reloadShieldsOnFontLoad(): void { + if (!this._fontsLoaded && !document.fonts.check(this._fontSpec)) { + document.fonts.load(this._fontSpec).then(() => this.onFontsLoaded()); + } else { + this._fontsLoaded = true; + } + } + /** Set a callback that fires when shield definitions are loaded */ public onShieldDefLoad( callback: (shields: ShieldDefinitions) => void @@ -166,7 +218,10 @@ export class AbstractShieldRenderer { return; } if (routeDef) { - missingIconLoader(this._renderContext, routeDef, e.id); + if (!this._fontsLoaded && routeDef.ref) { + this._preFontImageCache.set(e.id, routeDef); + } + missingIconLoader(this._renderContext, routeDef, e.id, false); } } catch (err) { console.error(`Exception while loading image ‘${e?.id}’:\n`, err); 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 63ae4ff1d..e4063e486 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 */ 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/fonts.css b/src/fonts.css index 166b520f5..89471403c 100644 --- a/src/fonts.css +++ b/src/fonts.css @@ -6,6 +6,7 @@ format("woff"); font-weight: 500; font-style: normal; + font-display: swap; } @font-face { font-family: "Noto Sans Armenian Condensed"; @@ -15,4 +16,5 @@ format("woff"); font-weight: 500; font-style: normal; + font-display: swap; } 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 -