js: Allow CSS to set the font used by the puzzle

This means that the calculated font properties of the HTML canvas now
control what font is used.  The size is overridden, and for monospaced
text so is the family.

I'd like to be able to also specify the monospaced font, maybe using a
CSS variable, but that looks like being quite a lot of extra complexity.

My experience when testing this was that constructing a valid "font"
string for a canvas context is prone to breakage, but broke in a way
that left the font unchanged, so we always set a simple specification
first before trying to construct one from CSS.
This commit is contained in:
Ben Harris
2022-12-05 14:02:59 +00:00
parent a3310ab857
commit 02f1d55a02
4 changed files with 38 additions and 22 deletions

15
emcc.c
View File

@ -72,10 +72,10 @@ extern void js_canvas_draw_poly(const int *points, int npoints,
extern void js_canvas_draw_circle(int x, int y, int r, extern void js_canvas_draw_circle(int x, int y, int r,
const char *fillcolour, const char *fillcolour,
const char *outlinecolour); const char *outlinecolour);
extern int js_canvas_find_font_midpoint(int height, const char *fontptr); extern int js_canvas_find_font_midpoint(int height, bool monospaced);
extern void js_canvas_draw_text(int x, int y, int halign, extern void js_canvas_draw_text(int x, int y, int halign,
const char *colptr, const char *fontptr, const char *colptr, int height,
const char *text); bool monospaced, const char *text);
extern int js_canvas_new_blitter(int w, int h); extern int js_canvas_new_blitter(int w, int h);
extern void js_canvas_free_blitter(int id); extern void js_canvas_free_blitter(int id);
extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h); extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
@ -444,14 +444,10 @@ static void js_draw_text(void *handle, int x, int y, int fonttype,
int fontsize, int align, int colour, int fontsize, int align, int colour,
const char *text) const char *text)
{ {
char fontstyle[80];
int halign; int halign;
sprintf(fontstyle, "%dpx %s", fontsize,
fonttype == FONT_FIXED ? "monospace" : "sans-serif");
if (align & ALIGN_VCENTRE) if (align & ALIGN_VCENTRE)
y += js_canvas_find_font_midpoint(fontsize, fontstyle); y += js_canvas_find_font_midpoint(fontsize, fonttype == FONT_FIXED);
if (align & ALIGN_HCENTRE) if (align & ALIGN_HCENTRE)
halign = 1; halign = 1;
@ -460,7 +456,8 @@ static void js_draw_text(void *handle, int x, int y, int fonttype,
else else
halign = 0; halign = 0;
js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text); js_canvas_draw_text(x, y, halign, colour_strings[colour],
fontsize, fonttype == FONT_FIXED, text);
} }
static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour) static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)

View File

@ -386,7 +386,7 @@ mergeInto(LibraryManager.library, {
}, },
/* /*
* int js_canvas_find_font_midpoint(int height, const char *fontptr); * int js_canvas_find_font_midpoint(int height, bool monospaced);
* *
* Return the adjustment required for text displayed using * Return the adjustment required for text displayed using
* ALIGN_VCENTRE. We want to place the midpoint between the * ALIGN_VCENTRE. We want to place the midpoint between the
@ -405,16 +405,17 @@ mergeInto(LibraryManager.library, {
* Since this is a very expensive operation, we cache the results * Since this is a very expensive operation, we cache the results
* per (font,height) pair. * per (font,height) pair.
*/ */
js_canvas_find_font_midpoint: function(height, font) { js_canvas_find_font_midpoint: function(height, monospaced) {
font = UTF8ToString(font);
// Resolve the font into a string.
var ctx1 = onscreen_canvas.getContext('2d', { alpha: false });
canvas_set_font(ctx1, height, monospaced);
// Reuse cached value if possible // Reuse cached value if possible
if (midpoint_cache[font] !== undefined) if (midpoint_cache[ctx1.font] !== undefined)
return midpoint_cache[font]; return midpoint_cache[ctx1.font];
// Find the width of the string // Find the width of the string
var ctx1 = onscreen_canvas.getContext('2d', { alpha: false });
ctx1.font = font;
var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0; var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0;
// Construct a test canvas of appropriate size, initialise it to // Construct a test canvas of appropriate size, initialise it to
@ -427,7 +428,7 @@ mergeInto(LibraryManager.library, {
ctx2.fillRect(0, 0, width, 2*height); ctx2.fillRect(0, 0, width, 2*height);
var baseline = (1.5*height) | 0; var baseline = (1.5*height) | 0;
ctx2.fillStyle = "#ffffff"; ctx2.fillStyle = "#ffffff";
ctx2.font = font; canvas_set_font(ctx2, height, monospaced);
ctx2.fillText(midpoint_test_str, 0, baseline); ctx2.fillText(midpoint_test_str, 0, baseline);
// Scan the contents of the test canvas to find the top and bottom // Scan the contents of the test canvas to find the top and bottom
@ -445,22 +446,23 @@ mergeInto(LibraryManager.library, {
} }
var ret = (baseline - (ymin + ymax) / 2) | 0; var ret = (baseline - (ymin + ymax) / 2) | 0;
midpoint_cache[font] = ret; midpoint_cache[ctx1.font] = ret;
return ret; return ret;
}, },
/* /*
* void js_canvas_draw_text(int x, int y, int halign, * void js_canvas_draw_text(int x, int y, int halign,
* const char *colptr, const char *fontptr, * const char *colptr, int height,
* const char *text); * bool monospaced, const char *text);
* *
* Draw text. Vertical alignment has been taken care of on the C * Draw text. Vertical alignment has been taken care of on the C
* side, by optionally calling the above function. Horizontal * side, by optionally calling the above function. Horizontal
* alignment is handled here, since we can get the canvas draw * alignment is handled here, since we can get the canvas draw
* function to do it for us with almost no extra effort. * function to do it for us with almost no extra effort.
*/ */
js_canvas_draw_text: function(x, y, halign, colptr, fontptr, text) { js_canvas_draw_text: function(x, y, halign, colptr, fontsize, monospaced,
ctx.font = UTF8ToString(fontptr); text) {
canvas_set_font(ctx, fontsize, monospaced);
ctx.fillStyle = UTF8ToString(colptr); ctx.fillStyle = UTF8ToString(colptr);
ctx.textAlign = (halign == 0 ? 'left' : ctx.textAlign = (halign == 0 ? 'left' :
halign == 1 ? 'center' : 'right'); halign == 1 ? 'center' : 'right');

View File

@ -179,6 +179,22 @@ function canvas_mouse_coords(event, element) {
return {x: rcoords.x * xscale, y: rcoords.y * yscale} return {x: rcoords.x * xscale, y: rcoords.y * yscale}
} }
// Set the font on a CanvasRenderingContext2d based on the CSS font
// for the canvas, the requested size, and whether we want something
// monospaced.
function canvas_set_font(ctx, size, monospaced) {
var s = window.getComputedStyle(onscreen_canvas);
// First set something that we're certain will work. Constructing
// the font string from the computed style is a bit fragile, so
// this acts as a fallback.
ctx.font = `${size}px ` + (monospaced ? "monospace" : "sans-serif");
// In CSS Fonts Module Level 4, "font-stretch" gets serialised as
// a percentage, which can't be used in
// CanvasRenderingContext2d.font, so we omit it.
ctx.font = `${s.fontStyle} ${s.fontWeight} ${size}px ` +
(monospaced ? "monospace" : s.fontFamily);
}
// Enable and disable items in the CSS menus. // Enable and disable items in the CSS menus.
function disable_menu_item(item, disabledFlag) { function disable_menu_item(item, disabledFlag) {
item.disabled = disabledFlag; item.disabled = disabledFlag;

View File

@ -305,6 +305,7 @@ main {
#puzzlecanvas { #puzzlecanvas {
display: block; display: block;
width: 100%; width: 100%;
/* This sets the font that will be used in the puzzle. */
font-family: sans-serif; font-family: sans-serif;
} }