From 6adc3de1c4cd17d58f339a5049c11ea29cefe698 Mon Sep 17 00:00:00 2001 From: "Felix W. Dekker" Date: Mon, 10 Aug 2020 19:56:28 +0200 Subject: [PATCH] Add fallback font Fixes #83. --- package.json | 2 +- src/main/index.html | 2 +- src/main/js/Common.ts | 10 +++--- src/main/js/Display.ts | 69 ++++++++++++++++++++++++++++++++++-------- src/main/js/Game.ts | 9 ++++-- src/main/js/Main.ts | 9 +++++- 6 files changed, 79 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 6729f52..4b687e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minesweeper", - "version": "0.80.0", + "version": "0.80.1", "description": "Just Minesweeper!", "author": "Felix W. Dekker", "browser": "dist/bundle.js", diff --git a/src/main/index.html b/src/main/index.html index f311b26..ad6b1de 100644 --- a/src/main/index.html +++ b/src/main/index.html @@ -151,6 +151,6 @@ - + diff --git a/src/main/js/Common.ts b/src/main/js/Common.ts index 7f0ddab..333720d 100644 --- a/src/main/js/Common.ts +++ b/src/main/js/Common.ts @@ -54,10 +54,12 @@ export function range(length: number, beginAt: number = 0): number[] { * * Taken from https://stackoverflow.com/a/35572620/ (CC BY-SA 3.0). * - * @param callback the function to invoke once the font has loaded + * @param onSuccess the function to invoke once the font has loaded + * @param onFailure the function to invoke if the font cannot be loaded * @param timeout the maximum time in milliseconds to wait for the font to load */ -export function waitForForkAwesome(callback: () => void, timeout: number | undefined = undefined): void { +export function waitForForkAwesome(onSuccess: () => void, onFailure: () => void, + timeout: number | undefined = undefined): void { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d")!; const fontSize = 36; @@ -81,9 +83,9 @@ export function waitForForkAwesome(callback: () => void, timeout: number | undef */ function fontOnload(time: number): void { const currentCount = getPixelCount(); - if (failTime !== undefined && time > failTime) alert(`ForkAwesome failed to load after ${timeout}ms.`); + if (failTime !== undefined && time > failTime) onFailure(); else if (currentCount < targetPixelCount) requestAnimationFrame(fontOnload); - else callback(); + else onSuccess(); } /** diff --git a/src/main/js/Display.ts b/src/main/js/Display.ts index ceba566..dddeb44 100644 --- a/src/main/js/Display.ts +++ b/src/main/js/Display.ts @@ -10,7 +10,9 @@ import {Field, Square} from "./Field"; export class Display { private readonly scale: number = 30; private readonly minSquareWidth: number = 6; + private readonly canvas: HTMLCanvasElement; + private readonly font: IconFont; private field: Field | null = null; private winTime: number | null = null; @@ -22,8 +24,8 @@ export class Display { private coverSymbol: HTMLCanvasElement | undefined; private flagSymbol: HTMLCanvasElement | undefined; - private mineSymbol: HTMLCanvasElement | undefined; private uncoveredMineSymbol: HTMLCanvasElement | undefined; + private mineSymbol: HTMLCanvasElement | undefined; private digitSymbols: HTMLCanvasElement[] | undefined; private clockSymbol: HTMLCanvasElement | undefined; private deathsSymbolA: HTMLCanvasElement | undefined; @@ -35,9 +37,12 @@ export class Display { * * @param canvas the canvas to draw the field in * @param field the field to draw + * @param font the font to draw symbols with */ - constructor(canvas: HTMLCanvasElement, field: Field | null) { + constructor(canvas: HTMLCanvasElement, field: Field | null, font: IconFont = new BasicIconFont()) { this.canvas = canvas; + this.font = font; + this.setField(field); } @@ -58,7 +63,7 @@ export class Display { ctx.font = `${Math.floor(this.scale * 0.55)}px ${font}`; ctx.textBaseline = "middle"; ctx.textAlign = "center"; - ctx.fillText(text, Math.floor(this.scale / 2), Math.floor(this.scale / 2)); + ctx.fillText(text, Math.floor(this.scale / 2), Math.floor(this.scale / 2), this.scale); ctx.restore(); }; @@ -67,17 +72,17 @@ export class Display { this.flagSymbol = createCanvas(this.scale, this.scale); ctx = this.flagSymbol.getContext("2d")!; - fillText("\uf024", "ForkAwesome", "#f00"); - - this.mineSymbol = createCanvas(this.scale, this.scale); - ctx = this.mineSymbol.getContext("2d")!; - fillText("\uf1e2", "ForkAwesome", "#00007b"); + fillText(this.font.flag, this.font.fontFace, "#f00"); this.uncoveredMineSymbol = createCanvas(this.scale, this.scale); ctx = this.uncoveredMineSymbol.getContext("2d")!; ctx.fillStyle = "#f00"; ctx.fillRect(1, 1, this.scale - 2, this.scale - 2); - fillText("\uf1e2", "ForkAwesome", "#000"); + fillText(this.font.uncoveredMine, this.font.fontFace, "#000"); + + this.mineSymbol = createCanvas(this.scale, this.scale); + ctx = this.mineSymbol.getContext("2d")!; + fillText(this.font.mine, this.font.fontFace, "#00007b"); const digitColors = ["", "#0000ff", "#007b00", "#ff0000", "#00007b", "#7b0000", "#007b7b", "#000000", "#7b7b7b"]; @@ -90,15 +95,15 @@ export class Display { this.clockSymbol = createCanvas(this.scale, this.scale); ctx = this.clockSymbol.getContext("2d")!; - fillText("\uf017", "ForkAwesome"); + fillText(this.font.clock, this.font.fontFace); this.deathsSymbolA = createCanvas(this.scale, this.scale); ctx = this.deathsSymbolA.getContext("2d")!; - fillText("\uf0f9", "ForkAwesome", "#fff"); + fillText(this.font.deaths, this.font.fontFace, "#fff"); this.deathsSymbolB = createCanvas(this.scale, this.scale); ctx = this.deathsSymbolB.getContext("2d")!; - fillText("\uf0f9", "ForkAwesome", "#f00"); + fillText(this.font.deaths, this.font.fontFace, "#f00"); } /** @@ -424,3 +429,43 @@ export class Display { } } } + + +/** + * A font that can be used to display icons for the display. + */ +export interface IconFont { + fontFace: string; + + flag: string; + uncoveredMine: string; + mine: string; + clock: string; + deaths: string; +} + +/** + * A basic font that can be displayed in any browser. + */ +export class BasicIconFont implements IconFont { + fontFace = "Courier New"; + + flag = "F"; + uncoveredMine = "X"; + mine = "Mines"; + clock = "Time"; + deaths = "Deaths"; +} + +/** + * ForkAwesome, which can be used on any browser that does not block external fonts. + */ +export class ForkAwesomeFont implements IconFont { + fontFace = "ForkAwesome"; + + flag = "\uf024"; + uncoveredMine = "\uf1e2"; + mine = "\uf1e2"; + clock = "\uf017"; + deaths = "\uf0f9"; +} diff --git a/src/main/js/Game.ts b/src/main/js/Game.ts index bdfb56f..862b790 100644 --- a/src/main/js/Game.ts +++ b/src/main/js/Game.ts @@ -4,7 +4,7 @@ import {$} from "@fwdekker/template"; import alea from "alea"; import {stringToHash} from "./Common"; import {customDifficulty, defaultDifficulty, difficulties} from "./Difficulty"; -import {Display} from "./Display"; +import {BasicIconFont, Display, ForkAwesomeFont} from "./Display"; import {Field} from "./Field"; import {Solver} from "./Solver"; import {LocalStatistics} from "./Statistics"; @@ -51,12 +51,15 @@ export class Game { /** * Constructs and starts a new game of Minesweeper. + * + * @param withForkAwesome whether ForkAwesome can be used */ - constructor() { + constructor(withForkAwesome: boolean = true) { this.canvas = $("#canvas"); this.field = null; // Placeholder until `initNewField` - this.display = new Display(this.canvas, this.field); + this.display = + new Display(this.canvas, this.field, withForkAwesome ? new ForkAwesomeFont() : new BasicIconFont()); this.display.startDrawLoop(); this.canvas.style.visibility = "unset"; diff --git a/src/main/js/Main.ts b/src/main/js/Main.ts index 11ece4e..377b376 100644 --- a/src/main/js/Main.ts +++ b/src/main/js/Main.ts @@ -23,5 +23,12 @@ doAfterLoad(() => { // Start game - waitForForkAwesome(() => new Game(), 3000); + waitForForkAwesome( + () => new Game(), + () => { + alert("External font could not be loaded. Using fallback font. Is a browser extension blocking fonts?"); + new Game(false); + }, + 3000 + ); });