Font Placements

Just venting.... What a pain. In this example, I want to determine the vertical space between the top of the central Power metric and the bottom of the inner arc. The white space so I can add a GRADE metric centered in the available space.

The issue is that different devices place fonts in different locations vertically. This varies by device, and even varies between Numeric Fonts (font # 5-8) and Text Fonts. For example, the 1050 and some others vertically center numeric font, leaving more whitespace above the top of the value like you see on the left. But many EDGE devices place the numeric fonts near the top of the fontHeight region like you see on the right. So the only way I know of to determine the whitespace is a complicated function that takes into account the specific product type, the string that is being rendered (does it have ascenders/descenders, the font being rendered (LARGE, THAI_MEDIUM, etc), and then return the vertical skew from font center to the actual top of the rendered text. Ugh. And hope the simulator actually performs the same rendering as the physical device so my code properly handles all these inconsistencies.

Ok, vent over.

  • I wasted lot of time - maybe even 50% of the time I worked on my 1st relevant datafield - to try to do the impossible, exactly as you're trying to do for a few months now (at least)

    I got to the point that I kind of gave up. I have my framework that tries to guess font size and positioning using data from the simulator.json, some manual overrides by me. I have no way to test it on most of the supported devices. If there will be a user who sends me screenshots from a specific device then I'll tweak it for that device. So far nobody complained out if 200k users, even though I know it's not perfect even on some of the devices I have.

    In one of the data fields I use (not mine) I reported similar issues with screenshots. A few days later a new version came out that significantly improved the layout.

    What I am saying is that you are totally right. This is the way to create good apps, but you can't do better than what the underlying framework provides. We can continue to open feature requests or bug reports (i.e why things look different in the sim and on the real device) but it doesn't look like Garmin cares. It's not Apple, the don't understand design and UX as some of the devs would like to. So I think there's a point in each developers life when they understand that these things are impossible to make perfect and it's a waste of time.

  • I carefully went thru all the EDGE devices in the simulator to figure out the subtle vertical placement differences. I have checks that understand if the string I intend to render is valid in the Numeric Fonts (new devices render all the characters in those fonts now) and/or if they have descenders. Then I know how much the font can expand and/or vertically skew. So that if you use VCENTER, it'll optimize the selected font that can fit in the provided space you've allowed. And the vertical skew to center it. It works well, but what a pain! Watches are different still. But I focus on cycling.

  • I would only include that code once and use constants that are defined in a device specific file. It does make compilation longer (builds for each device instead of only 1 general build) but makes the generated code smaller. 

  • It's also fairly easy to optimize this code without using device specific files (although ofc that would be even more efficient.)

    Assuming memory is not an issue, I think the main advantage of this kind of refactoring is to make the code easier to maintain and understand.

    // (*) note: I assume the default value for expand is 1
    // and the default value for skew is 0. If not, adjust accordingly
    
    var numericNonDescenderExpandFactor = 1;
    var numericNonDescenderSkewFactor = 0;
    
    var numericDescenderExpandFactor = 1;
    var numericDescenderSkewFactor = 0;
    
    var nonNumericNonDescenderExpandFactor = 1;
    var nonNumericNonDescenderSkewFactor = 0;
    
    var nonNumericDescenderExpandFactor = 1;
    var nonNumericDescenderSkewFactor = 0;
    
    switch (DEVNAME) {
        case "edgeexplore2":
            numericNonDescenderExpandFactor = 1.2;
            numericNonDescenderSkewFactor = -0.1;
    
            numericDescenderExpandFactor = 1;
            numericDescenderSkewFactor = 0.5;
    
            nonNumericNonDescenderExpandFactor = 1.8;
            nonNumericNonDescenderSkewFactor = -0.1;
    
            nonNumericDescenderExpandFactor = 1;
            nonNumericDescenderSkewFactor = -0.75;
            break;
        case "edge540":
            numericNonDescenderExpandFactor = 1.2;
            numericNonDescenderSkewFactor = 0;
    
            numericDescenderExpandFactor = 1;
            numericDescenderSkewFactor = 0.5;
    
            nonNumericNonDescenderExpandFactor = 1;
            nonNumericNonDescenderSkewFactor = 0.25;
    
            // (*)
            nonNumericDescenderExpandFactor = 0;
            nonNumericDescenderSkewFactor = 0;
            break;
        //...
    }
    
    if (isNumericFont && !hasDescenders) {
        expandFactor = numericNonDescenderExpandFactor;
        skewFactor = numericNonDescenderSkewFactor;
    } else if (isNumericFont) {
        expandFactor = numericDescenderExpandFactor;
        skewFactor = numericDescenderSkewFactor;
    } else if (!hasDescenders) {
        expandFactor = nonNumericNonDescenderExpandFactor;
        skewFactor = nonNumericNonDescenderSkewFactor;
    } else {
        expandFactor = nonNumericDescenderExpandFactor;
        skewFactor = nonNumericDescenderSkewFactor;
    }
    
    var expand = fontSize / (fontSize - expandFactor * descentSize);
    var skew = descentSize * skewFactor;

    It does make compilation longer (builds for each device instead of only 1 general build)

    Are you referring to the export process? Wouldn't each device (or part number) be built separately in any case?

    Or do you mean it's faster when switching devices during a normal build?

  • My solution to this issue was following:

    • I wrote a test app that simply draws the figure "0" vertically centered and also draws the ascent/descent
    • I make a screenshot in each font and on each device (simulator only though)
    • I analyse the screenshots pixel by pixel and detect "real" ascent / descent and also where the 0 is drawn in comparison to where I would expect it
    • I create per device definitions for all fonts based on my image analysis
    • I use those definitions to place all my texts

    This is really a lot of work for just a simple thing... but gives me a +/- 1 pixel perfect result as long as the simulator of a device is accurate which it is in most cases. But sometimes (only once until now) I have to ask a user to make real device screenshots for me to get a good result.