lvgl / lv_binding_micropython

LVGL binding for MicroPython
MIT License
250 stars 161 forks source link

Simulator not loaading dynamic fonts #276

Open kdschlosser opened 1 year ago

kdschlosser commented 1 year ago

As the title states the Simulator doesn't load fonts from the file system

https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?script_startup=https://raw.githubusercontent.com/lvgl/lvgl/091141885abb5fbfbf91d6b8b925d429722a5814/examples/header.py&script=https://raw.githubusercontent.com/lvgl/lvgl/091141885abb5fbfbf91d6b8b925d429722a5814/examples/widgets/roller/lv_example_roller_3.py

amirgon commented 1 year ago

@uraich Could you take a look?
He is referring to lv_example_roller_3.py where the script attempts to load font_montserrat_22 dynamically. That works locally on the unix port but not on the web simulator, probably because the path is incorrect.

kdschlosser commented 1 year ago

it doesn't work because there is no __file__ attribute. Not sure why that attribute is not set.

The attribute is set for imported modules. I am thinking something may have changed in how the script gets loaded

uraich commented 1 year ago

I checked the local version of example_roller_3.py and that works fine. If must have to do with the JavaScript version of the interpreter. I am using the method @amirgon showed me, that allows to find the font files relative to the directory in which the executable is found: script_path = __file__[:__file__.rfind('/')] if __file__.find('/') >= 0 else '.'

When I only import lvgl as lv and the display_driver then __file__ is defined (the Python script is called test.py): image

image

kdschlosser commented 1 year ago

The __file__ module attribute is available if you run it locally it appears. But it is not available in the online simulator. But the funny thing is this.

if you run this code in the simulator it works.


import lvgl as lv
import display_driver

script_path = display_driver.__file__[:display_driver.__file__.rfind('/')] if display_driver.__file__.find('/') >= 0 else '.'

print(script_path)

the __file__ attribute is available for the display_driver module. I have a hunch that the __file__ attribute doesn't get set until after the module has completely loaded. Kind of like how the script module is not available in sys.modules either.

uraich commented 1 year ago

@amirgon: Does the simulator access fonts on a different folder than script.py/./../assets/font ?

kdschlosser commented 1 year ago

it's not that. the problem is literally the __file__ attribute is not available to get a path from. it doesn't exist.

kdschlosser commented 1 year ago

check this link and look at the error. It points explicitly to line 15 saying the name doesn't exist.

https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?script_startup=https://raw.githubusercontent.com/lvgl/lvgl/091141885abb5fbfbf91d6b8b925d429722a5814/examples/header.py&script=https://raw.githubusercontent.com/lvgl/lvgl/091141885abb5fbfbf91d6b8b925d429722a5814/examples/widgets/roller/lv_example_roller_3.py&script_direct=0e62dde2aeafb9e732646f85745c5074cba66b93

kdschlosser commented 1 year ago

I am not sure why it doesn't exist because if you add this before that line


print(display_driver.__file__)

a path does get printed out.

amirgon commented 1 year ago

@amirgon: Does the simulator access fonts on a different folder than script.py/./../assets/font ?

@uraich I think there is an extra / that causes the problem. When __file__ is not available, script_path is set to ''.
The font path is set like this:

font_montserrat_22 = lv.font_load("S:" + script_path + "/../../assets/font/montserrat-22.fnt")

This fails:

lv.font_load("S:/../../assets/font/montserrat-22.fnt")

But after removing the /, relative path to the font actually works:

lv.font_load("S:../../assets/font/montserrat-22.fnt")

The simplest solution is to set script_path is set to '.' when __file__ is not available:

        try:
            script_path = __file__[:__file__.rfind('/')] if __file__.find('/') >= 0 else '.'
        except NameError:            
            script_path = '.'
kdschlosser commented 1 year ago

so why use the crazy file thing and not hard code the path?

if script_path is '' then the font never gets loaded. if __file__ is not available because it is in a try except block it never loads as well.

This entire code block isn't right.

        try:
            roller1.set_style_text_font(lv.font_montserrat_22,lv.PART.SELECTED)
        except:
            fs_drv = lv.fs_drv_t()
            fs_driver.fs_register(fs_drv, 'S')
            print("montserrat-22 not enabled in lv_conf.h, dynamically loading the font")

        # get the directory in which the script is running
        try:
            script_path = __file__[:__file__.rfind('/')] if __file__.find('/') >= 0 else '.'
        except NameError:
            print("Could not find script path")
            script_path = ''
        if script_path != '':
            try:
                font_montserrat_22 = lv.font_load("S:" + script_path + "/../../assets/font/montserrat-22.fnt")
                roller1.set_style_text_font(font_montserrat_22,lv.PART.SELECTED)        
            except:
                print("Cannot load font file montserrat-22.fnt")

because even if the font sets properly it still tried to load the font dynamically.

This is better code for handling the dynamic loading of the font.

        try:
            roller1.set_style_text_font(lv.font_montserrat_22 ,lv.PART.SELECTED)
        except (NameError, AttributeError):
            print(
                "montserrat-22 not enabled in lv_conf.h, "
                "attempting to dynamically load the font"
            )
            try:

                fs_drv = lv.fs_drv_t()
                fs_driver.fs_register(fs_drv, 'S')
                font_montserrat_22 = lv.font_load(
                    "S:../../assets/font/montserrat-22.fnt"
                )
                roller1.set_style_text_font(font_montserrat_22, lv.PART.SELECTED)
            except:
                print(
                    "Cannot load font file montserrat-22.fnt, "
                    "using default font instead"
                )
amirgon commented 1 year ago

This is better code for handling the dynamic loading of the font.

@kdschlosser Your suggestion is incorrect when running locally and current directory is different than the script's path. We use __file__ to allow running the script locally regardless of how current directory is set.

kdschlosser commented 1 year ago

OK the path to the font is statically set in a relative position to the script. what you are doing with the file thing is you are taking off the filename and then appending onto that the relative path. if you get the absolute path for ../../assets/font/montserrat-22.fnt and also for "S:" + __file__[:__file__.rfind('/')] + "/../../assets/font/montserrat-22.fnt" you will find they are the exact same path.

as an example..

lvgl/src/core/../misc

is identical to

lvgl/src/misc

which is what you are doing with the file the location of the font is in the same relative location to the script you are using a path that is partially absolute and partially relative.

This is an even better way of handling the problem

        try:
            roller1.set_style_text_font(lv.font_montserrat_22 ,lv.PART.SELECTED)
        except (NameError, AttributeError):
            print(
                "montserrat-22 not enabled in lv_conf.h, "
                "attempting to dynamically load the font"
            )
            try:
                script_path = __file__.split('/')[:-1]
            except NameError:
                script_path = []

            try:
                fs_drv = lv.fs_drv_t()
                fs_driver.fs_register(fs_drv, 'S')
                font_montserrat_22 = lv.font_load(
                    "S:{0}".format('/'.join(script_path + ['../../assets/font/montserrat-22.fnt']))
                )
                roller1.set_style_text_font(font_montserrat_22, lv.PART.SELECTED)
            except:
                print(
                    "Cannot load font file montserrat-22.fnt, "
                    "using default font instead"
                )

This way if __file__ exists and there is no '/' in it it doesn't matter because we are creating a list and if there is no slash it produces an empty list. and on the back end we join the 2 lists together and then add a '/' between each item in the list. If there is only 1 item in the list then no slash gets added. This handles __file__ not being defined, it also handles __file__ not having any slashes in it and it only tried to load the font dynamically if the font is not available.

This is the original code.. I commented why it is wrong.

try:
    roller1.set_style_text_font(lv.font_montserrat_22, lv.PART.SELECTED) <<< this gets tried and if it succeeds the code in the except block doesn't run
except:
    fs_drv = lv.fs_drv_t()
    fs_driver.fs_register(fs_drv, 'S')
    print(
        "montserrat-22 not enabled in lv_conf.h, dynamically loading the font"
        )

    # get the directory in which the script is running
try:
    script_path = __file__[:__file__.rfind('/')] if __file__.find('/') >= 0 else '.'   <<< even tho the font succeeded in loading this still gets run because it is at the same indentaation level as the try except block before it
except NameError:
    print("Could not find script path")
    script_path = ''

if script_path != '':
    try:
        font_montserrat_22 = lv.font_load(                <<< This end up failing because the fs_driver was never initialized
            "S:" + script_path + "/../../assets/font/montserrat-22.fnt"
            )
        roller1.set_style_text_font(font_montserrat_22, lv.PART.SELECTED)
    except:
        print("Cannot load font file montserrat-22.fnt")   <<< resulting in this message getting displayed even tho the correct font initially loaded in the first place

you can see they are at the same indent level here,,

image