How to convert a C lvgl object to a micropython one?

STATIC mp_obj_t myfont(mp_obj_t path_obj) {
	char *path = mp_obj_str_get_str(path_obj);
	FILE *fp = fopen(path, 'rb');
	lv_font_t myf = init_my_font(fp);
	return ???;

What should I put in ???

I tried including lv_mpy.c and using ptr_to_mp, but lots of errors emerged.

Hi @TsXor !

Could you give us some more details on what you are trying to do?
What’s the goal in your case to initialize the font in C? You could read it from file in pure Python if you want.

In general, if you would like to automatically generate Micropython API for your C functions, the simplest way is to:

  • Add your C files to the makefile/cmake scripts
  • Include your public API C headers in lvgl.h

When building lv_micropython, lvgl.h is scanned and Micropython API is automatically generated. So if you extend lvgl.h with your new API, you could use it later through the lvgl module.

Another more advanced option is to auto-generate a new Micropython module from your API.
As an example you can look at mkrules.cmake which auto generates Micropython API for parts of the ESP-IDF.

It is a dynamic module so it cannot use C functions of lvgl.
However I eventually managed to do it:

  1. include lvgl.h

  2. define a C function binded to python, getting callback functions and python class object at python side.

STATIC mp_obj_t assembler(mp_obj_t font_type, mp_obj_t dsc_cb, mp_obj_t bmp_cb)
assembler(font_t, dsc_cb, bmp_cb)
  1. manually copy the definition of mp_lv_struct_t from lv_mpy_example.c
typedef struct mp_lv_struct_t
    mp_obj_base_t base;
    void *data;
} mp_lv_struct_t;
  1. define a struct to hold the pointers of callback functions
struct my_dsc {
	mp_obj_t dsc_cb;
	mp_obj_t bmp_cb;
  1. create a lv_font_t at C side, put the struct in step 4 to the dsc
	struct my_dsc *dsc = (struct my_dsc *)m_malloc(sizeof(struct my_dsc));
	dsc->dsc_cb = dsc_cb; dsc->bmp_cb = bmp_cb;
	lv_font_t *lv_font_obj = (lv_font_t *)m_malloc(sizeof(lv_font_t));
	lv_font_obj->dsc = dsc;
  1. “reversely wrap” the bound methods into C functions
bool get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next) {
	mp_obj_t args[1]; args[0] = mp_obj_new_int(unicode_letter);
	mp_obj_t size_obj = mp_call_function_n_kw(((struct my_dsc *)font->dsc)->dsc_cb, 1, 0, args);
	mp_buffer_info_t bufinfo;
	mp_get_buffer_raise(size_obj, &bufinfo, MP_BUFFER_READ);
	struct sizetup *size = bufinfo.buf;

	dsc_out->adv_w = size->w;   /*Horizontal space required by the glyph in [px]*/
	dsc_out->box_h = size->h;   /*Height of the bitmap in [px]*/
	dsc_out->box_w = size->w;   /*Width of the bitmap in [px]*/
	dsc_out->ofs_x = 0;         /*X offset of the bitmap in [pf]*/
	dsc_out->ofs_y = 0;         /*Y offset of the bitmap measured from the as line*/
	dsc_out->bpp   = 8;         /*Bits per pixel: 1/2/4/8*/

	return true;

const uint8_t * get_glyph_bitmap_cb(const lv_font_t * font, uint32_t unicode_letter) {
	mp_obj_t args[1]; args[0] = mp_obj_new_int(unicode_letter);
	mp_obj_t bmp_obj = mp_call_function_n_kw(((struct my_dsc *)font->dsc)->bmp_cb, 1, 0, args);
	mp_buffer_info_t bufinfo;
	mp_get_buffer_raise(bmp_obj, &bufinfo, MP_BUFFER_READ);
	uint8_t *bitmap = bufinfo.buf;

	return bitmap;
  1. give them to lv_font_t in C
	lv_font_obj->get_glyph_dsc = get_glyph_dsc_cb;
	lv_font_obj->get_glyph_bitmap = get_glyph_bitmap_cb;
  1. The magic part: assemble. Now manually make an mp_lv_struct_t from lv_font_t.
	mp_obj_base_t *tbase = (mp_obj_base_t *)m_malloc(sizeof(mp_obj_base_t));
	tbase->type = (mp_obj_type_t *)font_type;
	mp_lv_struct_t *pyf = (mp_lv_struct_t *)m_malloc(sizeof(mp_lv_struct_t));
	pyf->base = *tbase;
	pyf->data = lv_font_obj;

That’s why it is needed to get the lvgl.font_t object from python side: dynamic module cannot use static C things defined in lvgl, but we can get it in python and pass it to C.

  1. return the wrapped object
	return (mp_obj_t)pyf;

I’m writing a “font engine” as a dynamic module so that I don’t need to recompile the whole firmware