Skip to content

Commit f09b814

Browse files
authored
HTML: Fixes to Custom Font Positioning (#862)
1 parent 4914f7d commit f09b814

File tree

2 files changed

+126
-63
lines changed

2 files changed

+126
-63
lines changed

platform/emscripten/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Buld test app & quick setup
1+
# Build test app & quick setup
22

33
## Setting up
44
1. download [emscripten](http://emscripten.org) & unpack it

platform/emscripten/Rtt_EmscriptenPlatform.js

Lines changed: 125 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -807,149 +807,212 @@ var platformLibrary =
807807

808808
var fontName = UTF8ToString(_fontName);
809809
var a = fontName.split('/');
810-
var fontName = a[a.length - 1]; // filename
810+
fontName = a[a.length - 1]; // filename
811811

812-
var a = fontName.split('.');
812+
a = fontName.split('.');
813813
fontName = a[0];
814814
var ext = a[1];
815815

816816
var canva = document.createElement('canvas');
817-
canva.width = canvas.width;
818-
canva.height = canvas.height;
819-
canva.style.position = "absolute";
820817
var ctx = canva.getContext("2d");
821818

822819
if (Module.isSafari) {
823820
ctx.fillStyle = 'red';
824821
}
825822

826823
// check if font exists
827-
// the text whose final pixel size I want to measure
828824
var testtext = "ABCM|abcdefghijklmnopqrstuvwxyz0123456789";
829825

830-
// specifying the baseline font
831826
ctx.font = "72px monospace";
832-
833-
// checking the size of the baseline text
834827
var baselineSize = ctx.measureText(testtext).width;
835828

836-
// specifying the font whose existence we want to check
837829
ctx.font = "72px '" + fontName + "', monospace";
838-
839-
// checking the size of the font we want to check
840830
var newSize = ctx.measureText(testtext).width;
841831

842-
// If the size of the two text instances is the same, the font does not exist because it is being rendered
843-
fontExist = newSize != baselineSize;
832+
var fontExist = newSize != baselineSize;
844833

845834
if (fontName === '' || fontExist == false) {
846-
// console.log(fontName + ' not found, using sans-serif');
847-
fontName = 'sans-serif'; // Default value
835+
// console.log(fontName + ' not found, using sans-serif');
836+
fontName = 'sans-serif';
848837
}
849-
ctx.font = String(fontSize) + 'px ' + fontName;
850838

851-
ctx.textBaseline = 'top';
839+
// ensure font is loaded (prevents Firefox fallback metrics)
840+
if (document.fonts && document.fonts.load) {
841+
try {
842+
document.fonts.load(fontSize + 'px "' + fontName + '"');
843+
} catch (e) {
844+
// ignore
845+
}
846+
}
847+
848+
ctx.font = String(fontSize) + 'px ' + fontName;
852849
ctx.textAlign = alignment;
850+
ctx.textBaseline = 'alphabetic'; // safer baseline across browsers
851+
852+
// measure proper font metrics for vertical alignment
853+
var testMetrics = ctx.measureText("Mg'");
854+
var ascent = testMetrics.actualBoundingBoxAscent || fontSize * 0.75;
855+
var descent = testMetrics.actualBoundingBoxDescent || fontSize * 0.25;
856+
var lineHeight = ascent + descent;
857+
858+
// Extra space for punctuation that extends above normal ascent
859+
var topExtraPadding = fontSize * 0.2;
853860

854-
var a = measureText(testtext, false, fontName, fontSize);
855-
var lineHeight = a[1];
861+
// Anti-aliasing padding
862+
var horizontalPadding = Math.ceil(fontSize * 0.15);
856863

864+
// calculate actual content dimensions first
865+
var maxWidth = w;
866+
var useFixedWidth = (w > 0);
867+
857868
if (w == 0) {
858-
// calc width
869+
// calc width dynamically
870+
maxWidth = 0;
859871
var line = '';
860872
for (var i = 0; i < text.length; i++) {
861873
if (text.charAt(i) == '\n') {
862874
line = '';
863-
}
864-
else {
875+
} else {
865876
line += text.charAt(i);
866877
var metrics = ctx.measureText(line);
867-
if (metrics.width > w) {
868-
w = metrics.width;
878+
if (metrics.width > maxWidth) {
879+
maxWidth = metrics.width;
869880
}
870881
}
871882
}
872-
// last line
873883
var metrics = ctx.measureText(line);
874-
w = Math.max(w, metrics.width);
884+
maxWidth = Math.max(maxWidth, metrics.width);
875885
}
876886

877-
var x = 0;
878-
var y = 0;
879-
if (alignment === 'right') {
880-
x = w;
881-
}
882-
else
883-
if (alignment === 'center') {
884-
x = w / 2;
887+
// count lines to calculate height
888+
var lineCount = 1;
889+
var line = '';
890+
var effectiveMaxWidth = useFixedWidth ? (w - (horizontalPadding * 2)) : maxWidth;
891+
892+
for (var i = 0; i < text.length; i++) {
893+
if (text.charAt(i) == '\n') {
894+
lineCount++;
895+
line = '';
896+
} else {
897+
var testLine = line + text.charAt(i);
898+
var metrics = ctx.measureText(testLine);
899+
if (metrics.width > effectiveMaxWidth) {
900+
lineCount++;
901+
if (text.charAt(i) === ' ') {
902+
line = '';
903+
} else {
904+
var a = line.split(' ');
905+
if (a.length > 1) {
906+
line = a[a.length - 1] + text.charAt(i);
907+
} else {
908+
line = text.charAt(i);
909+
}
910+
}
911+
} else {
912+
line = testLine;
913+
}
885914
}
915+
}
916+
917+
// calculate required dimensions with proper padding
918+
var topPadding = topExtraPadding; // padding to prevent clipping
919+
var calculatedHeight = (lineCount * lineHeight) + topPadding;
920+
var calculatedWidth = maxWidth + (horizontalPadding * 2);
921+
922+
// set canvas to exact required size
923+
canva.width = useFixedWidth ? w : Math.ceil(calculatedWidth);
924+
canva.height = h > 0 ? h : Math.ceil(calculatedHeight);
925+
canva.style.position = "absolute";
926+
927+
// re-apply font and styles after canvas resize (canvas resets context)
928+
ctx.font = String(fontSize) + 'px ' + fontName;
929+
ctx.textAlign = alignment;
930+
ctx.textBaseline = 'alphabetic';
931+
932+
if (Module.isSafari) {
933+
ctx.fillStyle = 'red';
934+
}
886935

887-
// wrap text
936+
var y = ascent + topPadding;
937+
var x = 0;
938+
939+
if (alignment === 'left') {
940+
x = horizontalPadding;
941+
} else if (alignment === 'right') {
942+
x = canva.width - horizontalPadding;
943+
} else if (alignment === 'center') {
944+
x = canva.width / 2;
945+
}
888946

889-
var ww = 0;
890-
var hh = 0;
947+
// render text
891948
var line = '';
892949
for (var i = 0; i < text.length; i++) {
893950
if (text.charAt(i) == '\n') {
894951
ctx.fillText(line, x, y);
895952
line = '';
896953
y += lineHeight;
897-
}
898-
else {
954+
} else {
899955
var testLine = line + text.charAt(i);
900956
var metrics = ctx.measureText(testLine);
901-
if (metrics.width > w) {
957+
if (metrics.width > effectiveMaxWidth) {
902958
if (text.charAt(i) === ' ') {
903959
// ignore last space
904960
ctx.fillText(line, x, y);
905961
line = '';
906-
}
907-
else {
962+
} else {
908963
// delete last uncomplete word if space exists
909964
var a = line.split(' ');
910-
if (a.length > 1) {
911-
var line = a[a.length - 1] + text.charAt(i); // the beginning of next line
912-
a.pop(); // remove last
965+
if (a.length > 1) {
966+
line = a[a.length - 1] + text.charAt(i); // beginning of next line
967+
a.pop();
913968
var s = a.join(' ');
914969
ctx.fillText(s, x, y);
915-
}
916-
else{
917-
// no words, draw line as is
970+
} else {
971+
// no words, draw line as is
918972
ctx.fillText(line, x, y);
919-
line = text.charAt(i); // the beginning of next line
973+
line = text.charAt(i);
920974
}
921975
}
922976
y += lineHeight;
923-
}
924-
else {
977+
} else {
925978
line = testLine;
926979
}
927980
}
928981
}
929982

930983
// last line
931-
ctx.fillText(line, x, y);
984+
ctx.fillText(line, Math.round(x), Math.round(y));
932985

933-
hh = h > 0 ? h : y + lineHeight;
934-
ww = w > 0 ? w : 1;
986+
var ww = canva.width;
987+
var hh = canva.height;
935988

936-
// it's needs for corona ?
989+
// make width 4-byte aligned
937990
if ((ww & 0x3) != 0) {
938991
ww = (ww + 3) & -4;
939992
}
940993

941-
//console.log('render: ', metrics, text, w, h, ww, hh, alignment, fontName, fontSize);
994+
// console.log('render: ', text, w, h, ww, hh, alignment, fontName, fontSize);
942995

943-
var myImageData = ctx.getImageData(0, 0, ww, hh);
996+
var myImageData = ctx.getImageData(0, 0, canva.width, canva.height);
944997
var img = Module.jarray2carray(myImageData.data);
945-
_jsEmscriptenBitmapSaveImage(thiz, myImageData.data.length, img, myImageData.width, myImageData.height, Module.isSafari);
998+
_jsEmscriptenBitmapSaveImage(
999+
thiz,
1000+
myImageData.data.length,
1001+
img,
1002+
myImageData.width,
1003+
myImageData.height,
1004+
Module.isSafari
1005+
);
9461006
_free(img);
9471007

948-
//var body = document.getElementsByTagName("body")[0];
949-
//body.appendChild(canva);
950-
//canva.remove();
1008+
// optional debug
1009+
// var body = document.getElementsByTagName("body")[0];
1010+
// body.appendChild(canva);
1011+
// canva.remove();
9511012
},
9521013

1014+
1015+
9531016
jsContextSetClearColor: function(r, g, b, a)
9541017
{
9551018
var rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')';

0 commit comments

Comments
 (0)