As covered in many discussions, the functions dc.getFontHeight() and dc.getTextDimensions() do not return the actual height of the build in Garmin fonts.
This has annoyed me, since I’d like to be able to use biggest number font possible for a given screen area in my data fields.
So I tried to see if I could find some “systematic” way of approximating the actual height for number fonts, knowing that Garmin reuse the same type of number fonts across multiple watches. What I learned is that there is indeed a “system” across most watches. That is:
- For watches where dc.getFontDescent() return 0, height is equal to dc.getFontAscent()
- Otherwise, height is approximate equal to a scalar multiplied by dc.getFontDescent()+dc.getFontAscent() – where the scalar depends on type of number font
I then furthermore leverage that the dot '.' and percentage '%' characters have a height/width ratio that varies depending on type of number font. And use this to select an appropriate scalar. This approach results in an accuracy of +/- 1 pixel.
I thought others might find this code useful, hence this post to share.
Limitations: Works for build in number fonts only (no custom, not text fonts). And designed and verified for newer SDK 3.0+ watches only. Not Edge, nor outdoor/handhelds devices. And I have not bothered with descentg1 and instinct watches. See code for the exact devices verified. And, obviously, there are no guarantees this code will work for future new watches.
For the few watches this does not work for (Venu series, Vivoactive 2 series and Forerunner 55), code has been added to manage these special cases.
Height definition: Height I have defined as how much of the screen is drawn upon from the baseline to the top of text. See the green rectangle below (Vivoactive 3). Note some number fonts though go “a bit” below the baseline for some digits. For those I have defined the height to exclude the “similar” extra bit at the top (the two to the right). Due to this, and due to the +/-1 pixel accuracy, make sure to have extra space above and below numbers which is always a good idea for readability.
This code is used to print the above example:
H = getApproxNumberFontHeightInPixels(dc, NumberFont);
dc.drawRectangle(x, dc.getHeight()/2 – H + 1, dc.getTextDimensions("8", NumberFont)[0], H);
dc.drawText(x, dc.getHeight()/2 - dc.getFontAscent(NumberFont), NumberFont, ".", Graphics.TEXT_JUSTIFY_LEFT);
Input much appreciated! And, whether there is a desire for the code to be maintained as new watches are launched.
The code:
function getApproxNumberFontHeightInPixels(dc, NumberFont) {
// Returns the appoximate height for a number font with an accuracy of +/- 1 pixel
// NumberFont must be either 5, 6, 7 or 8 (between Graphics.FONT_NUMBER_MILD and Graphics.FONT_NUMBER_THAI_HOT)
// Works for all SDK 3.0+ Garmin watches, except for: descentg1, instinct2 & instinct2s, instinctcrossover
// Does not work for Edge, nor outdoor/handhelds devices. All devices supported are listed in the comments below
var Height; var Ascent; var Descent; var DotRatio; var PercRatio;
Ascent = dc.getFontAscent(NumberFont);
Descent = dc.getFontDescent(NumberFont);
DotRatio = dc.getTextDimensions(".", Graphics.FONT_NUMBER_THAI_HOT);
DotRatio = DotRatio[1].toFloat() / DotRatio[0]; // Dot '.' character height/width ratio
PercRatio = dc.getTextDimensions("%", Graphics.FONT_NUMBER_THAI_HOT);
PercRatio = PercRatio[1].toFloat() / PercRatio[0]; // Percentage '%' character height/width ratio
if (Descent==0) {
// For fonts with no Descent, then Height=dc.getFontAscent(NumberFont);
// Applies for: fr935, fr645, fr645m, fenix5, fenix5plus, fenix5splus, fenix5xplus, fenix5x, fenixchronos,
// d2charlie, d2delta, d2deltapx, d2deltas, descentmk1
Height = Ascent;
} else {
if (DotRatio < 5.0) {
// For fonts with Dot ratio below 5.0
// Applies (with PercRatio>1.7) for: fr245, fr245m, fr255, fr255m, fr255s, fr255sm, fr745, fr945, fr945lte, fr955, approachs62
// vivoactive4, legacyherofirstavenger, legacysagadarthvader, vivoactive4s, legacyherocaptainmarvel, legacysagarey
// Applies (with PercRatio<1.7) for: fr265, fr265s, fr965
Height = (Ascent + Descent) * (NumberFont<=6 ? 0.600 : (PercRatio>1.7 ? 0.550 : 0.595));
} else {
// For fonts with Dot ratio above 5.0
// Applies for: fenix7, fenix7s, fenix7x, epix2, d2mach1, marq2, marq2aviator,
// fenix6, fenix6s, fenix6pro, fenix6spro, fenix6xpro, descentmk2, descentmk2s, enduro,
// marqadventurer, marqathlete, marqaviator, marqcaptain, marqcommander, marqdriver, marqexpedition, marqgolfer,
// venu2, venu2plus, d2airx10, venu2s, venusq, venusqm, venusq2, venusq2m
Height = (Ascent + Descent) * 0.50;
}
// Special case handling:
var i = NumberFont-5;
var partNumber = System.getDeviceSettings().partNumber;
if (partNumber.equals("006-B2700-00") or partNumber.equals("006-B2976-00") or partNumber.equals("006-B3446-00") or // vivoactive3
partNumber.equals("006-B2988-00") or partNumber.equals("006-B3163-00") or partNumber.equals("006-B3066-00") or // vivoactive3m, vivoactive3mlte
partNumber.equals("006-B3473-00") or partNumber.equals("006-B3477-00")) { // vivoactive3d
Height = [21, 25, 40, 49][i];
}
if (partNumber.equals("006-B3226-00") or partNumber.equals("006-B3389-00") or // venu
partNumber.equals("006-B3740-00") or partNumber.equals("006-B3737-00") or partNumber.equals("006-B2187-00")) { // venud, d2air
Height = [43, 51, 78, 92][i];
}
if (partNumber.equals("006-B3869-00") or partNumber.equals("006-B4033-00")) { // fr55
Height = [20, 43, 49, 49][i];
}
}
return Math.round(Height).toNumber();
}