From 7fd75d64956f82b9d17451294ef33cd05e362333 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Sat, 25 Oct 2025 21:56:28 -0400 Subject: [PATCH 1/3] HTML: Fixes to Custom Font Positioning --- platform/emscripten/README.md | 2 +- platform/emscripten/Rtt_EmscriptenPlatform.js | 168 ++++++++++++------ 2 files changed, 110 insertions(+), 60 deletions(-) diff --git a/platform/emscripten/README.md b/platform/emscripten/README.md index 744982750..a77dc4075 100644 --- a/platform/emscripten/README.md +++ b/platform/emscripten/README.md @@ -1,4 +1,4 @@ -# Buld test app & quick setup +# Build test app & quick setup ## Setting up 1. download [emscripten](http://emscripten.org) & unpack it diff --git a/platform/emscripten/Rtt_EmscriptenPlatform.js b/platform/emscripten/Rtt_EmscriptenPlatform.js index f26ff1aad..cb2f28330 100644 --- a/platform/emscripten/Rtt_EmscriptenPlatform.js +++ b/platform/emscripten/Rtt_EmscriptenPlatform.js @@ -807,16 +807,13 @@ var platformLibrary = var fontName = UTF8ToString(_fontName); var a = fontName.split('/'); - var fontName = a[a.length - 1]; // filename + fontName = a[a.length - 1]; // filename - var a = fontName.split('.'); + a = fontName.split('.'); fontName = a[0]; var ext = a[1]; var canva = document.createElement('canvas'); - canva.width = canvas.width; - canva.height = canvas.height; - canva.style.position = "absolute"; var ctx = canva.getContext("2d"); if (Module.isSafari) { @@ -824,104 +821,148 @@ var platformLibrary = } // check if font exists - // the text whose final pixel size I want to measure var testtext = "ABCM|abcdefghijklmnopqrstuvwxyz0123456789"; - // specifying the baseline font ctx.font = "72px monospace"; - - // checking the size of the baseline text var baselineSize = ctx.measureText(testtext).width; - // specifying the font whose existence we want to check ctx.font = "72px '" + fontName + "', monospace"; - - // checking the size of the font we want to check var newSize = ctx.measureText(testtext).width; - // If the size of the two text instances is the same, the font does not exist because it is being rendered - fontExist = newSize != baselineSize; + var fontExist = newSize != baselineSize; if (fontName === '' || fontExist == false) { - // console.log(fontName + ' not found, using sans-serif'); - fontName = 'sans-serif'; // Default value + // console.log(fontName + ' not found, using sans-serif'); + fontName = 'sans-serif'; } - ctx.font = String(fontSize) + 'px ' + fontName; - ctx.textBaseline = 'top'; + // ensure font is loaded (prevents Firefox fallback metrics) + if (document.fonts && document.fonts.load) { + try { + document.fonts.load(fontSize + 'px "' + fontName + '"'); + } catch (e) { + // ignore + } + } + + ctx.font = String(fontSize) + 'px ' + fontName; ctx.textAlign = alignment; + ctx.textBaseline = 'alphabetic'; // safer baseline across browsers - var a = measureText(testtext, false, fontName, fontSize); - var lineHeight = a[1]; + // measure proper font metrics for vertical alignment + var testMetrics = ctx.measureText("Mg"); + var ascent = testMetrics.actualBoundingBoxAscent || fontSize * 0.75; + var descent = testMetrics.actualBoundingBoxDescent || fontSize * 0.25; + var lineHeight = ascent + descent; + // calculate actual content dimensions first + var maxWidth = w; if (w == 0) { - // calc width + // calc width dynamically + maxWidth = 0; var line = ''; for (var i = 0; i < text.length; i++) { if (text.charAt(i) == '\n') { line = ''; - } - else { + } else { line += text.charAt(i); var metrics = ctx.measureText(line); - if (metrics.width > w) { - w = metrics.width; + if (metrics.width > maxWidth) { + maxWidth = metrics.width; } } } - // last line var metrics = ctx.measureText(line); - w = Math.max(w, metrics.width); + maxWidth = Math.max(maxWidth, metrics.width); + } + + // count lines to calculate height + var lineCount = 1; + var line = ''; + for (var i = 0; i < text.length; i++) { + if (text.charAt(i) == '\n') { + lineCount++; + line = ''; + } else { + var testLine = line + text.charAt(i); + var metrics = ctx.measureText(testLine); + if (metrics.width > maxWidth) { + lineCount++; + if (text.charAt(i) === ' ') { + line = ''; + } else { + var a = line.split(' '); + if (a.length > 1) { + line = a[a.length - 1] + text.charAt(i); + } else { + line = text.charAt(i); + } + } + } else { + line = testLine; + } + } + } + + // calculate required dimensions with proper padding + var topPadding = fontSize * 0.15; // padding to prevent clipping + var calculatedHeight = (lineCount * lineHeight) + (descent * 0.5); + var calculatedWidth = maxWidth; + + // set canvas to exact required size + canva.width = h > 0 ? w : Math.ceil(calculatedWidth); + canva.height = h > 0 ? h : Math.ceil(calculatedHeight); + canva.style.position = "absolute"; + + // re-apply font and styles after canvas resize (canvas resets context) + ctx.font = String(fontSize) + 'px ' + fontName; + ctx.textAlign = alignment; + ctx.textBaseline = 'alphabetic'; + + if (Module.isSafari) { + ctx.fillStyle = 'red'; } var x = 0; - var y = 0; + var y = ascent + topPadding; + if (alignment === 'right') { - x = w; + x = maxWidth; + } else if (alignment === 'center') { + x = maxWidth / 2; } - else - if (alignment === 'center') { - x = w / 2; - } - - // wrap text - var ww = 0; - var hh = 0; + // render text var line = ''; for (var i = 0; i < text.length; i++) { if (text.charAt(i) == '\n') { ctx.fillText(line, x, y); line = ''; y += lineHeight; - } - else { + } else { var testLine = line + text.charAt(i); var metrics = ctx.measureText(testLine); - if (metrics.width > w) { + if (metrics.width > maxWidth) { if (text.charAt(i) === ' ') { // ignore last space ctx.fillText(line, x, y); line = ''; - } - else { + } else { // delete last uncomplete word if space exists var a = line.split(' '); - if (a.length > 1) { - var line = a[a.length - 1] + text.charAt(i); // the beginning of next line - a.pop(); // remove last + if (a.length > 1) { + line = a[a.length - 1] + text.charAt(i); // beginning of next line + a.pop(); var s = a.join(' '); ctx.fillText(s, x, y); - } - else{ - // no words, draw line as is + } else { + // no words, draw line as is ctx.fillText(line, x, y); - line = text.charAt(i); // the beginning of next line + line = text.charAt(i); } } y += lineHeight; - } - else { + } else { line = testLine; } } @@ -930,26 +971,35 @@ var platformLibrary = // last line ctx.fillText(line, x, y); - hh = h > 0 ? h : y + lineHeight; - ww = w > 0 ? w : 1; + var ww = w > 0 ? w : canva.width; + var hh = h > 0 ? h : canva.height; - // it's needs for corona ? + // make width 4-byte aligned if ((ww & 0x3) != 0) { ww = (ww + 3) & -4; } - //console.log('render: ', metrics, text, w, h, ww, hh, alignment, fontName, fontSize); + // console.log('render: ', text, w, h, ww, hh, alignment, fontName, fontSize); var myImageData = ctx.getImageData(0, 0, ww, hh); var img = Module.jarray2carray(myImageData.data); - _jsEmscriptenBitmapSaveImage(thiz, myImageData.data.length, img, myImageData.width, myImageData.height, Module.isSafari); + _jsEmscriptenBitmapSaveImage( + thiz, + myImageData.data.length, + img, + myImageData.width, + myImageData.height, + Module.isSafari + ); _free(img); - //var body = document.getElementsByTagName("body")[0]; - //body.appendChild(canva); - //canva.remove(); + // optional debug + // var body = document.getElementsByTagName("body")[0]; + // body.appendChild(canva); + // canva.remove(); }, + jsContextSetClearColor: function(r, g, b, a) { var rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; From 40feb772747a607f94677dd4af61e7c8220fda7e Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Wed, 29 Oct 2025 15:59:56 -0500 Subject: [PATCH 2/3] Fixes for Chrome Bleed --- platform/emscripten/Rtt_EmscriptenPlatform.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/platform/emscripten/Rtt_EmscriptenPlatform.js b/platform/emscripten/Rtt_EmscriptenPlatform.js index cb2f28330..f4f2af0d5 100644 --- a/platform/emscripten/Rtt_EmscriptenPlatform.js +++ b/platform/emscripten/Rtt_EmscriptenPlatform.js @@ -855,6 +855,9 @@ var platformLibrary = var descent = testMetrics.actualBoundingBoxDescent || fontSize * 0.25; var lineHeight = ascent + descent; + // Anti-aliasing padding + var horizontalPadding = Math.ceil(fontSize * 0.15); + // calculate actual content dimensions first var maxWidth = w; if (w == 0) { @@ -874,6 +877,9 @@ var platformLibrary = } var metrics = ctx.measureText(line); maxWidth = Math.max(maxWidth, metrics.width); + } else { + // If fixed width, reduce maxWidth to account for padding + maxWidth = w - (horizontalPadding * 2); } // count lines to calculate height @@ -907,7 +913,7 @@ var platformLibrary = // calculate required dimensions with proper padding var topPadding = fontSize * 0.15; // padding to prevent clipping var calculatedHeight = (lineCount * lineHeight) + (descent * 0.5); - var calculatedWidth = maxWidth; + var calculatedWidth = maxWidth + (horizontalPadding * 2); // set canvas to exact required size canva.width = h > 0 ? w : Math.ceil(calculatedWidth); @@ -923,13 +929,13 @@ var platformLibrary = ctx.fillStyle = 'red'; } - var x = 0; + var x = horizontalPadding; var y = ascent + topPadding; if (alignment === 'right') { - x = maxWidth; + x = maxWidth + horizontalPadding; } else if (alignment === 'center') { - x = maxWidth / 2; + x = (maxWidth / 2) + horizontalPadding; } // render text @@ -969,10 +975,10 @@ var platformLibrary = } // last line - ctx.fillText(line, x, y); + ctx.fillText(line, Math.round(x), Math.round(y)); - var ww = w > 0 ? w : canva.width; - var hh = h > 0 ? h : canva.height; + var ww = canva.width; + var hh = canva.height; // make width 4-byte aligned if ((ww & 0x3) != 0) { @@ -981,7 +987,7 @@ var platformLibrary = // console.log('render: ', text, w, h, ww, hh, alignment, fontName, fontSize); - var myImageData = ctx.getImageData(0, 0, ww, hh); + var myImageData = ctx.getImageData(0, 0, canva.width, canva.height); var img = Module.jarray2carray(myImageData.data); _jsEmscriptenBitmapSaveImage( thiz, From 595ff8cccbe54a5f097744def9d7be48c003b5a1 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Wed, 29 Oct 2025 23:12:26 -0500 Subject: [PATCH 3/3] Fixes --- platform/emscripten/Rtt_EmscriptenPlatform.js | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/platform/emscripten/Rtt_EmscriptenPlatform.js b/platform/emscripten/Rtt_EmscriptenPlatform.js index f4f2af0d5..191b5c2a5 100644 --- a/platform/emscripten/Rtt_EmscriptenPlatform.js +++ b/platform/emscripten/Rtt_EmscriptenPlatform.js @@ -850,16 +850,21 @@ var platformLibrary = ctx.textBaseline = 'alphabetic'; // safer baseline across browsers // measure proper font metrics for vertical alignment - var testMetrics = ctx.measureText("Mg"); + var testMetrics = ctx.measureText("Mg'"); var ascent = testMetrics.actualBoundingBoxAscent || fontSize * 0.75; var descent = testMetrics.actualBoundingBoxDescent || fontSize * 0.25; var lineHeight = ascent + descent; + + // Extra space for punctuation that extends above normal ascent + var topExtraPadding = fontSize * 0.2; // Anti-aliasing padding var horizontalPadding = Math.ceil(fontSize * 0.15); // calculate actual content dimensions first var maxWidth = w; + var useFixedWidth = (w > 0); + if (w == 0) { // calc width dynamically maxWidth = 0; @@ -877,14 +882,13 @@ var platformLibrary = } var metrics = ctx.measureText(line); maxWidth = Math.max(maxWidth, metrics.width); - } else { - // If fixed width, reduce maxWidth to account for padding - maxWidth = w - (horizontalPadding * 2); } // count lines to calculate height var lineCount = 1; var line = ''; + var effectiveMaxWidth = useFixedWidth ? (w - (horizontalPadding * 2)) : maxWidth; + for (var i = 0; i < text.length; i++) { if (text.charAt(i) == '\n') { lineCount++; @@ -892,7 +896,7 @@ var platformLibrary = } else { var testLine = line + text.charAt(i); var metrics = ctx.measureText(testLine); - if (metrics.width > maxWidth) { + if (metrics.width > effectiveMaxWidth) { lineCount++; if (text.charAt(i) === ' ') { line = ''; @@ -911,12 +915,12 @@ var platformLibrary = } // calculate required dimensions with proper padding - var topPadding = fontSize * 0.15; // padding to prevent clipping - var calculatedHeight = (lineCount * lineHeight) + (descent * 0.5); + var topPadding = topExtraPadding; // padding to prevent clipping + var calculatedHeight = (lineCount * lineHeight) + topPadding; var calculatedWidth = maxWidth + (horizontalPadding * 2); // set canvas to exact required size - canva.width = h > 0 ? w : Math.ceil(calculatedWidth); + canva.width = useFixedWidth ? w : Math.ceil(calculatedWidth); canva.height = h > 0 ? h : Math.ceil(calculatedHeight); canva.style.position = "absolute"; @@ -929,13 +933,15 @@ var platformLibrary = ctx.fillStyle = 'red'; } - var x = horizontalPadding; var y = ascent + topPadding; + var x = 0; - if (alignment === 'right') { - x = maxWidth + horizontalPadding; + if (alignment === 'left') { + x = horizontalPadding; + } else if (alignment === 'right') { + x = canva.width - horizontalPadding; } else if (alignment === 'center') { - x = (maxWidth / 2) + horizontalPadding; + x = canva.width / 2; } // render text @@ -948,7 +954,7 @@ var platformLibrary = } else { var testLine = line + text.charAt(i); var metrics = ctx.measureText(testLine); - if (metrics.width > maxWidth) { + if (metrics.width > effectiveMaxWidth) { if (text.charAt(i) === ' ') { // ignore last space ctx.fillText(line, x, y); @@ -1006,6 +1012,7 @@ var platformLibrary = }, + jsContextSetClearColor: function(r, g, b, a) { var rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')';