diff --git a/.github/actions/deps/ports/broadcom/action.yml b/.github/actions/deps/ports/broadcom/action.yml index 99f53064b3f68..f936f8e7edfde 100644 --- a/.github/actions/deps/ports/broadcom/action.yml +++ b/.github/actions/deps/ports/broadcom/action.yml @@ -5,8 +5,8 @@ runs: steps: - name: Get broadcom toolchain run: | - wget --no-verbose https://adafruit-circuit-python.s3.amazonaws.com/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz - sudo tar -C /usr --strip-components=1 -xaf gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz + wget --no-verbose https://adafruit-circuit-python.s3.amazonaws.com/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-elf.tar.xz + sudo tar -C /usr --strip-components=1 -xaf arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-elf.tar.xz sudo apt-get update sudo apt-get install -y mtools shell: bash diff --git a/.gitignore b/.gitignore index 9679ed5e0e2bc..6725b901b92bf 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,8 @@ TAGS # clangd cache ############## .cache + +**/CLAUDE.local.md + +# windsurf rules +.windsurfrules diff --git a/docs/environment.rst b/docs/environment.rst index 82d7e8790bcd8..442c340f1a4d4 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -189,4 +189,18 @@ This feature is not enabled on boards that the CIRCUITPY_OS_GETENV (os CIRCUIPTY flag has been set to 0. Currently this is primarily boards with limited flash including some of the Atmel_samd boards based on the SAMD21/M0 microprocessor. +CIRCUITPY_TERMINAL_FONT +~~~~~~~~~~~~~~~~~~~~~~~ +Specifies a custom font file path to use for the terminalio console instead of the default +``/fonts/terminal.lvfontbin``. This allows users to create and use custom fonts for the +CircuitPython console. + +This feature requires both CIRCUITPY_OS_GETENV and CIRCUITPY_LVFONTIO to be enabled. + +Example: + +.. code-block:: + + CIRCUITPY_TERMINAL_FONT="/fonts/myfont.lvfontbin" + `boards that the terminalio core module is available on `_ diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 167fb20ff9e80..49920b1549524 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1051,7 +1051,7 @@ msgstr "" msgid "File exists" msgstr "" -#: shared-module/os/getenv.c +#: shared-module/lvfontio/OnDiskFont.c shared-module/os/getenv.c msgid "File not found" msgstr "" @@ -1247,6 +1247,7 @@ msgstr "" #: shared-bindings/digitalio/DigitalInOut.c #: shared-bindings/epaperdisplay/EPaperDisplay.c shared-bindings/pwmio/PWMOut.c #: shared-module/aurora_epaper/aurora_framebuffer.c +#: shared-module/lvfontio/OnDiskFont.c msgid "Invalid %q" msgstr "" @@ -1975,6 +1976,7 @@ msgstr "" #: shared-bindings/displayio/TileGrid.c #: shared-bindings/memorymonitor/AllocationSize.c #: shared-bindings/pulseio/PulseIn.c +#: shared-bindings/tilepalettemapper/TilePaletteMapper.c msgid "Slices not supported" msgstr "" @@ -2042,7 +2044,9 @@ msgstr "" msgid "Tile height must exactly divide bitmap height" msgstr "" -#: shared-bindings/displayio/TileGrid.c shared-module/displayio/TileGrid.c +#: shared-bindings/displayio/TileGrid.c +#: shared-bindings/tilepalettemapper/TilePaletteMapper.c +#: shared-module/displayio/TileGrid.c msgid "Tile index out of bounds" msgstr "" @@ -4277,7 +4281,9 @@ msgstr "" msgid "unreadable attribute" msgstr "" -#: shared-bindings/displayio/TileGrid.c shared-bindings/vectorio/VectorShape.c +#: shared-bindings/displayio/TileGrid.c shared-bindings/terminalio/Terminal.c +#: shared-bindings/tilepalettemapper/TilePaletteMapper.c +#: shared-bindings/vectorio/VectorShape.c msgid "unsupported %q type" msgstr "" diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 15013b2d831ca..861ef37464633 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -12,6 +12,7 @@ CIRCUITPY_OPTIMIZE_PROPERTY_FLASH_SIZE ?= 1 CIRCUITPY_LTO = 1 CIRCUITPY_KEYPAD_DEMUX ?= 0 +CIRCUITPY_LVFONTIO ?= 0 ###################################################################### # Put samd21-only choices here. diff --git a/ports/raspberrypi/common-hal/picodvi/__init__.c b/ports/raspberrypi/common-hal/picodvi/__init__.c index 9a77979669463..e8f344852de96 100644 --- a/ports/raspberrypi/common-hal/picodvi/__init__.c +++ b/ports/raspberrypi/common-hal/picodvi/__init__.c @@ -74,11 +74,11 @@ static bool picodvi_autoconstruct_enabled(mp_int_t *default_width, mp_int_t *def if ((established_timings & 0x80) != 0 && preferred_width % 1920 == 0 && preferred_height % 1080 == 0) { - *default_width = 720 / 2; - *default_height = 400 / 2; + *default_width = 720; + *default_height = 400; } else { - *default_width = 640 / 2; - *default_height = 480 / 2; + *default_width = 640; + *default_height = 480; } } } @@ -95,15 +95,15 @@ void picodvi_autoconstruct(void) { return; } - mp_int_t default_width = 320; - mp_int_t default_height = 240; + mp_int_t default_width = 640; + mp_int_t default_height = 480; if (!picodvi_autoconstruct_enabled(&default_width, &default_height)) { return; } mp_int_t width = default_width; mp_int_t height = 0; - mp_int_t color_depth = 16; + mp_int_t color_depth = 8; mp_int_t rotation = 0; (void)common_hal_os_getenv_int("CIRCUITPY_DISPLAY_WIDTH", &width); @@ -113,6 +113,9 @@ void picodvi_autoconstruct(void) { if (height == 0) { switch (width) { + case 720: + height = 400; + break; case 640: height = 480; break; @@ -134,7 +137,7 @@ void picodvi_autoconstruct(void) { // invalid configuration, set back to default width = default_width; height = default_height; - color_depth = 16; + color_depth = 8; } // construct framebuffer and display diff --git a/ports/stm/boards/meowbit_v121/mpconfigboard.mk b/ports/stm/boards/meowbit_v121/mpconfigboard.mk index bde49ad13ef49..3a128fb8343cc 100644 --- a/ports/stm/boards/meowbit_v121/mpconfigboard.mk +++ b/ports/stm/boards/meowbit_v121/mpconfigboard.mk @@ -27,6 +27,8 @@ CIRCUITPY_BITMAPFILTER = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BLEIO_HCI = 0 CIRCUITPY_EPAPERDISPLAY = 0 +CIRCUITPY_FRAMEBUFFERIO = 0 +CIRCUITPY_I2CDISPLAYBUS = 0 CIRCUITPY_KEYPAD_DEMUX = 0 CIRCUITPY_SHARPDISPLAY = 0 CIRCUITPY_TILEPALETTEMAPPER = 0 diff --git a/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk b/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk index f34fac4047a72..98d13f592ffcb 100644 --- a/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk +++ b/ports/stm/boards/stm32f411ve_discovery/mpconfigboard.mk @@ -9,6 +9,8 @@ MCU_SERIES = F4 MCU_VARIANT = STM32F411xE MCU_PACKAGE = LQFP100_f4 +OPTIMIZATION_FLAGS = -Os + LD_COMMON = boards/common_default.ld LD_FILE = boards/STM32F411_fs.ld diff --git a/ports/stm/boards/thunderpack_v11/mpconfigboard.mk b/ports/stm/boards/thunderpack_v11/mpconfigboard.mk index 4fede00dc07ea..0107bbb1c1777 100644 --- a/ports/stm/boards/thunderpack_v11/mpconfigboard.mk +++ b/ports/stm/boards/thunderpack_v11/mpconfigboard.mk @@ -13,6 +13,8 @@ MCU_SERIES = F4 MCU_VARIANT = STM32F411xE MCU_PACKAGE = UFQFPN48 +OPTIMIZATION_FLAGS = -Os + LD_COMMON = boards/common_nvm.ld LD_FILE = boards/STM32F411_nvm.ld diff --git a/ports/stm/boards/thunderpack_v12/mpconfigboard.mk b/ports/stm/boards/thunderpack_v12/mpconfigboard.mk index 276cf6d011050..353d0733e6483 100644 --- a/ports/stm/boards/thunderpack_v12/mpconfigboard.mk +++ b/ports/stm/boards/thunderpack_v12/mpconfigboard.mk @@ -25,11 +25,7 @@ MCU_SERIES = F4 MCU_VARIANT = STM32F411xE MCU_PACKAGE = UFQFPN48 +OPTIMIZATION_FLAGS = -Os + LD_COMMON = boards/common_nvm.ld LD_FILE = boards/STM32F411_nvm_nofs.ld - -# Disable TERMINALIO on translations with missing characters. -ifneq (,$(filter $(TRANSLATION),ja ko ru)) -CIRCUITPY_TERMINALIO = 0 -RELEASE_NEEDS_CLEAN_BUILD = $(CIRCUITPY_DISPLAYIO) -endif diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index ba0291f64300a..624e7e69a3fd1 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index 88178af3b28c2..07ab1f7f4dfc6 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index 85b5a9c7e0fad..b10bbe69a0a10 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 674a19ab107c9..fcfeec097d124 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 4f920c59f11d9..69481e904fea4 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index 000155eafa744..e7e179e4c2cc9 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index ac0aff61a59c3..d4fa229b7e67d 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -58,6 +58,7 @@ jpegio = false keypad = false keypad_demux = false locale = false +lvfontio = false math = false max3421e = false mdns = false diff --git a/ports/zephyr-cp/cptools/update_board_info.py b/ports/zephyr-cp/cptools/update_board_info.py new file mode 100755 index 0000000000000..935fc07c17d3a --- /dev/null +++ b/ports/zephyr-cp/cptools/update_board_info.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +import pathlib +import sys +import tomlkit + + +def find_modules(top_dir, port_dir): + """Find all available modules in shared-bindings and port bindings.""" + modules = set() + for module in sorted( + list(top_dir.glob("shared-bindings/*")) + list(port_dir.glob("bindings/*")), + key=lambda x: x.name, + ): + if not module.is_dir(): + continue + modules.add(module.name) + return sorted(modules) + + +def find_board_info_files(port_dir): + """Find all autogen_board_info.toml files in the port directory.""" + return list(port_dir.glob("boards/**/autogen_board_info.toml")) + + +def update_board_info(board_info_path, available_modules): + """Update board info file with new modules set to false.""" + if not board_info_path.exists(): + print(f"Error: Board info file {board_info_path} does not exist", file=sys.stderr) + return False + + # Load existing board info + with open(board_info_path, "r", encoding="utf-8") as f: + board_info = tomlkit.load(f) + + # Get current modules + current_modules = set(board_info.get("modules", {})) + + # Find new modules + new_modules = set(available_modules) - current_modules + if not new_modules: + print( + f"No new modules found for {board_info_path.relative_to(board_info_path.parents[3])}" + ) + return True + + # Add new modules as disabled in alphabetical order + modules_table = board_info["modules"] + # Get all modules (existing and new) and sort them + all_modules = list(current_modules | new_modules) + all_modules.sort() + + # Create a new table with sorted modules + sorted_table = tomlkit.table() + for module in all_modules: + if module in modules_table: + # TODO: Use modules_table.item once tomlkit is released with changes from January 2025 + sorted_table[module] = modules_table._value.item(module) + else: + sorted_table[module] = tomlkit.item(False) + + # Replace the modules table with the sorted one + board_info["modules"] = sorted_table + + # Write updated board info + with open(board_info_path, "w", encoding="utf-8") as f: + tomlkit.dump(board_info, f) + + print( + f"Updated {board_info_path.relative_to(board_info_path.parents[3])} with {len(new_modules)} new modules:" + ) + for module in sorted(new_modules): + print(f" - {module}") + return True + + +def main(): + # Get repo paths + script_dir = pathlib.Path(__file__).parent + top_dir = script_dir.parents[2] # circuitpython root + port_dir = script_dir.parent # zephyr-cp directory + + # Get available modules once + available_modules = find_modules(top_dir, port_dir) + + # Update all board info files + board_info_files = find_board_info_files(port_dir) + if not board_info_files: + print("No board info files found") + sys.exit(1) + + success = True + for board_info_path in board_info_files: + if not update_board_info(board_info_path, available_modules): + success = False + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 513f7d2d64d97..8fb0c1c8185f1 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -396,6 +396,9 @@ endif ifeq ($(CIRCUITPY_FONTIO),1) SRC_PATTERNS += fontio/% endif +ifeq ($(CIRCUITPY_LVFONTIO),1) +SRC_PATTERNS += lvfontio/% +endif ifeq ($(CIRCUITPY_TILEPALETTEMAPPER),1) SRC_PATTERNS += tilepalettemapper/% endif @@ -675,6 +678,8 @@ SRC_SHARED_MODULE_ALL = \ floppyio/__init__.c \ fontio/BuiltinFont.c \ fontio/__init__.c \ + lvfontio/OnDiskFont.c\ + lvfontio/__init__.c \ fourwire/__init__.c \ fourwire/FourWire.c \ framebufferio/FramebufferDisplay.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 831c1ec245fc3..52520215b24d7 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -555,9 +555,12 @@ CFLAGS += -DCIRCUITPY_TERMINALIO=$(CIRCUITPY_TERMINALIO) CIRCUITPY_TERMINALIO_VT100 ?= $(CIRCUITPY_TERMINALIO) CFLAGS += -DCIRCUITPY_TERMINALIO_VT100=$(CIRCUITPY_TERMINALIO_VT100) -CIRCUITPY_FONTIO ?= $(call enable-if-all,$(CIRCUITPY_DISPLAYIO) $(CIRCUITPY_TERMINALIO)) +CIRCUITPY_FONTIO ?= $(CIRCUITPY_TERMINALIO) CFLAGS += -DCIRCUITPY_FONTIO=$(CIRCUITPY_FONTIO) +CIRCUITPY_LVFONTIO ?= $(CIRCUITPY_TERMINALIO) +CFLAGS += -DCIRCUITPY_LVFONTIO=$(CIRCUITPY_LVFONTIO) + CIRCUITPY_TILEPALETTEMAPPER ?= $(CIRCUITPY_DISPLAYIO) CFLAGS += -DCIRCUITPY_TILEPALETTEMAPPER=$(CIRCUITPY_TILEPALETTEMAPPER) diff --git a/shared-bindings/displayio/TileGrid.c b/shared-bindings/displayio/TileGrid.c index a8757fbcc17f2..fbc995fc49b89 100644 --- a/shared-bindings/displayio/TileGrid.c +++ b/shared-bindings/displayio/TileGrid.c @@ -62,7 +62,8 @@ void displayio_tilegrid_validate_pixel_shader(mp_obj_t pixel_shader) { //| convert the value and its location to a display native pixel color. This may be a simple color //| palette lookup, a gradient, a pattern or a color transformer. //| -//| To save RAM usage, tile values are only allowed in the range from 0 to 255 inclusive (single byte values). +//| When the total number of tiles is 256 or less, tile values are stored as single bytes (uint8_t). +//| When the total number of tiles is more than 256, tile values are stored as double bytes (uint16_t). //| //| tile_width and tile_height match the height of the bitmap by default. //| @@ -453,7 +454,7 @@ static mp_obj_t tilegrid_subscr(mp_obj_t self_in, mp_obj_t index_obj, mp_obj_t v return MP_OBJ_NULL; // op not supported } else { mp_int_t value = mp_obj_get_int(value_obj); - mp_arg_validate_int_range(value, 0, 255, MP_QSTR_tile); + mp_arg_validate_int_range(value, 0, self->tiles_in_bitmap - 1, MP_QSTR_tile); common_hal_displayio_tilegrid_set_tile(self, x, y, value); } diff --git a/shared-bindings/displayio/TileGrid.h b/shared-bindings/displayio/TileGrid.h index 19af39fadeb37..35b2b2fd00c9e 100644 --- a/shared-bindings/displayio/TileGrid.h +++ b/shared-bindings/displayio/TileGrid.h @@ -13,7 +13,7 @@ extern const mp_obj_type_t displayio_tilegrid_type; void common_hal_displayio_tilegrid_construct(displayio_tilegrid_t *self, mp_obj_t bitmap, uint16_t bitmap_width_in_tiles, uint16_t bitmap_height_in_tiles, mp_obj_t pixel_shader, uint16_t width, uint16_t height, - uint16_t tile_width, uint16_t tile_height, uint16_t x, uint16_t y, uint8_t default_tile); + uint16_t tile_width, uint16_t tile_height, uint16_t x, uint16_t y, uint16_t default_tile); bool common_hal_displayio_tilegrid_get_hidden(displayio_tilegrid_t *self); void common_hal_displayio_tilegrid_set_hidden(displayio_tilegrid_t *self, bool hidden); @@ -43,9 +43,9 @@ uint16_t common_hal_displayio_tilegrid_get_height(displayio_tilegrid_t *self); uint16_t common_hal_displayio_tilegrid_get_tile_width(displayio_tilegrid_t *self); uint16_t common_hal_displayio_tilegrid_get_tile_height(displayio_tilegrid_t *self); -uint8_t common_hal_displayio_tilegrid_get_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y); -void common_hal_displayio_tilegrid_set_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y, uint8_t tile_index); +uint16_t common_hal_displayio_tilegrid_get_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y); +void common_hal_displayio_tilegrid_set_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y, uint16_t tile_index); // Private API for scrolling the TileGrid. void common_hal_displayio_tilegrid_set_top_left(displayio_tilegrid_t *self, uint16_t x, uint16_t y); -void common_hal_displayio_tilegrid_set_all_tiles(displayio_tilegrid_t *self, uint8_t tile_index); +void common_hal_displayio_tilegrid_set_all_tiles(displayio_tilegrid_t *self, uint16_t tile_index); diff --git a/shared-bindings/lvfontio/OnDiskFont.c b/shared-bindings/lvfontio/OnDiskFont.c new file mode 100644 index 0000000000000..3d4df234429ff --- /dev/null +++ b/shared-bindings/lvfontio/OnDiskFont.c @@ -0,0 +1,94 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/lvfontio/OnDiskFont.h" + +#include + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/objproperty.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/util.h" + +//| class OnDiskFont: +//| """A font built into CircuitPython for use with LVGL""" +//| +//| def __init__(self, file_path: str, max_glyphs: int = 100) -> None: +//| """Create a OnDiskFont by loading an LVGL font file from the filesystem. +//| +//| :param str file_path: The path to the font file +//| :param int max_glyphs: Maximum number of glyphs to cache at once +//| """ +//| ... +//| + +//| bitmap: displayio.Bitmap +//| """Bitmap containing all font glyphs starting with ASCII and followed by unicode. This is useful for use with LVGL.""" +//| +static mp_obj_t lvfontio_ondiskfont_obj_get_bitmap(mp_obj_t self_in) { + lvfontio_ondiskfont_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_lvfontio_ondiskfont_get_bitmap(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(lvfontio_ondiskfont_get_bitmap_obj, lvfontio_ondiskfont_obj_get_bitmap); + +MP_PROPERTY_GETTER(lvfontio_ondiskfont_bitmap_obj, + (mp_obj_t)&lvfontio_ondiskfont_get_bitmap_obj); + +//| def get_bounding_box(self) -> Tuple[int, int]: +//| """Returns the maximum bounds of all glyphs in the font in a tuple of two values: width, height.""" +//| ... +//| +//| +static mp_obj_t lvfontio_ondiskfont_obj_get_bounding_box(mp_obj_t self_in) { + lvfontio_ondiskfont_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_lvfontio_ondiskfont_get_bounding_box(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(lvfontio_ondiskfont_get_bounding_box_obj, lvfontio_ondiskfont_obj_get_bounding_box); + +static const mp_rom_map_elem_t lvfontio_ondiskfont_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&lvfontio_ondiskfont_bitmap_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_bounding_box), MP_ROM_PTR(&lvfontio_ondiskfont_get_bounding_box_obj) }, +}; +static MP_DEFINE_CONST_DICT(lvfontio_ondiskfont_locals_dict, lvfontio_ondiskfont_locals_dict_table); + +static mp_obj_t lvfontio_ondiskfont_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_file_path, ARG_max_glyphs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_file_path, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_max_glyphs, MP_ARG_INT, {.u_int = 100} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Allocate the BuiltinFont object + lvfontio_ondiskfont_t *self = m_new_obj(lvfontio_ondiskfont_t); + self->base.type = &lvfontio_ondiskfont_type; + + // Extract arguments + mp_obj_t file_path_obj = args[ARG_file_path].u_obj; + mp_uint_t max_glyphs = args[ARG_max_glyphs].u_int; + + // Get the C string from the Python string + const char *file_path = mp_obj_str_get_str(file_path_obj); + + // Always use GC allocator for Python-created objects + common_hal_lvfontio_ondiskfont_construct(self, file_path, max_glyphs, true); + + return MP_OBJ_FROM_PTR(self); +} + +MP_DEFINE_CONST_OBJ_TYPE( + lvfontio_ondiskfont_type, + MP_QSTR_OnDiskFont, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, lvfontio_ondiskfont_make_new, + locals_dict, &lvfontio_ondiskfont_locals_dict + ); diff --git a/shared-bindings/lvfontio/OnDiskFont.h b/shared-bindings/lvfontio/OnDiskFont.h new file mode 100644 index 0000000000000..d7dac6ac6787a --- /dev/null +++ b/shared-bindings/lvfontio/OnDiskFont.h @@ -0,0 +1,22 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/lvfontio/OnDiskFont.h" + +extern const mp_obj_type_t lvfontio_ondiskfont_type; + +mp_obj_t common_hal_lvfontio_ondiskfont_get_bitmap(const lvfontio_ondiskfont_t *self); +mp_obj_t common_hal_lvfontio_ondiskfont_get_bounding_box(const lvfontio_ondiskfont_t *self); +void common_hal_lvfontio_ondiskfont_get_dimensions(const lvfontio_ondiskfont_t *self, uint16_t *width, uint16_t *height); + +// Function prototypes +void common_hal_lvfontio_ondiskfont_construct(lvfontio_ondiskfont_t *self, const char *file_path, uint16_t max_glyphs, bool use_gc_allocator); +void common_hal_lvfontio_ondiskfont_deinit(lvfontio_ondiskfont_t *self); +bool common_hal_lvfontio_ondiskfont_deinited(lvfontio_ondiskfont_t *self); +int16_t common_hal_lvfontio_ondiskfont_cache_glyph(lvfontio_ondiskfont_t *self, uint32_t codepoint, bool *is_full_width); +void common_hal_lvfontio_ondiskfont_release_glyph(lvfontio_ondiskfont_t *self, uint32_t slot); diff --git a/shared-bindings/lvfontio/__init__.c b/shared-bindings/lvfontio/__init__.c new file mode 100644 index 0000000000000..ec5f352279952 --- /dev/null +++ b/shared-bindings/lvfontio/__init__.c @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/lvfontio/__init__.h" +#include "shared-bindings/lvfontio/OnDiskFont.h" + +//| """Core font related data structures for LVGL +//| +//| .. note:: This module is intended only for low-level usage with LVGL. +//| +//| """ + +static const mp_rom_map_elem_t lvfontio_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_lvfontio) }, + { MP_ROM_QSTR(MP_QSTR_OnDiskFont), MP_ROM_PTR(&lvfontio_ondiskfont_type) }, +}; + +static MP_DEFINE_CONST_DICT(lvfontio_module_globals, lvfontio_module_globals_table); + +const mp_obj_module_t lvfontio_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&lvfontio_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_lvfontio, lvfontio_module); diff --git a/shared-bindings/lvfontio/__init__.h b/shared-bindings/lvfontio/__init__.h new file mode 100644 index 0000000000000..b56388fa8ae3a --- /dev/null +++ b/shared-bindings/lvfontio/__init__.h @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once diff --git a/shared-bindings/terminalio/Terminal.c b/shared-bindings/terminalio/Terminal.c index 570449ad424a4..63cabda7fbed8 100644 --- a/shared-bindings/terminalio/Terminal.c +++ b/shared-bindings/terminalio/Terminal.c @@ -16,6 +16,10 @@ #include "py/stream.h" #include "shared-bindings/fontio/BuiltinFont.h" +#if CIRCUITPY_LVFONTIO +#include "shared-bindings/lvfontio/OnDiskFont.h" +#endif + //| class Terminal: //| """Display a character stream with a TileGrid //| @@ -86,7 +90,26 @@ static mp_obj_t terminalio_terminal_make_new(const mp_obj_type_t *type, size_t n status_bar = mp_arg_validate_type(args[ARG_status_bar].u_obj, &displayio_tilegrid_type, MP_QSTR_status_bar); } - fontio_builtinfont_t *font = mp_arg_validate_type(args[ARG_font].u_obj, &fontio_builtinfont_type, MP_QSTR_font); + mp_obj_t font = args[ARG_font].u_obj; + + // Ensure the font is one of the supported types + bool valid_font = false; + + #if CIRCUITPY_FONTIO + if (mp_obj_is_type(font, &fontio_builtinfont_type)) { + valid_font = true; + } + #endif + + #if CIRCUITPY_LVFONTIO + if (mp_obj_is_type(font, &lvfontio_ondiskfont_type)) { + valid_font = true; + } + #endif + + if (!valid_font) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("unsupported %q type"), MP_QSTR_font); + } mp_arg_validate_int_min(scroll_area->width_in_tiles, 2, MP_QSTR_scroll_area_width); mp_arg_validate_int_min(scroll_area->height_in_tiles, 2, MP_QSTR_scroll_area_height); diff --git a/shared-bindings/terminalio/Terminal.h b/shared-bindings/terminalio/Terminal.h index 893db4d131128..c99cae2b3bb8c 100644 --- a/shared-bindings/terminalio/Terminal.h +++ b/shared-bindings/terminalio/Terminal.h @@ -13,7 +13,7 @@ extern const mp_obj_type_t terminalio_terminal_type; extern void common_hal_terminalio_terminal_construct(terminalio_terminal_obj_t *self, - displayio_tilegrid_t *scroll_area, const fontio_builtinfont_t *font, displayio_tilegrid_t *status_bar); + displayio_tilegrid_t *scroll_area, mp_obj_t font, displayio_tilegrid_t *status_bar); // Write characters. len is in characters NOT bytes! extern size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, diff --git a/shared-module/displayio/TileGrid.c b/shared-module/displayio/TileGrid.c index e0d18d0accfb8..9312b3e04748b 100644 --- a/shared-module/displayio/TileGrid.c +++ b/shared-module/displayio/TileGrid.c @@ -15,29 +15,53 @@ #include "shared-bindings/tilepalettemapper/TilePaletteMapper.h" #endif +#include "supervisor/shared/serial.h" + void common_hal_displayio_tilegrid_construct(displayio_tilegrid_t *self, mp_obj_t bitmap, uint16_t bitmap_width_in_tiles, uint16_t bitmap_height_in_tiles, mp_obj_t pixel_shader, uint16_t width, uint16_t height, - uint16_t tile_width, uint16_t tile_height, uint16_t x, uint16_t y, uint8_t default_tile) { + uint16_t tile_width, uint16_t tile_height, uint16_t x, uint16_t y, uint16_t default_tile) { + uint32_t total_tiles = width * height; + self->bitmap_width_in_tiles = bitmap_width_in_tiles; + self->tiles_in_bitmap = bitmap_width_in_tiles * bitmap_height_in_tiles; + + // Determine if we need uint16_t or uint8_t for tile indices + bool use_uint16 = self->tiles_in_bitmap > 255; + // Sprites will only have one tile so save a little memory by inlining values in the pointer. - uint8_t inline_tiles = sizeof(uint8_t *); + uint8_t inline_tiles = sizeof(void *) / (use_uint16 ? sizeof(uint16_t) : sizeof(uint8_t)); + if (total_tiles <= inline_tiles) { self->tiles = 0; // Pack values into the pointer since there are only a few. - for (uint32_t i = 0; i < inline_tiles; i++) { - ((uint8_t *)&self->tiles)[i] = default_tile; + if (use_uint16) { + for (uint32_t i = 0; i < inline_tiles && i < total_tiles; i++) { + ((uint16_t *)&self->tiles)[i] = default_tile; + } + } else { + for (uint32_t i = 0; i < inline_tiles && i < total_tiles; i++) { + ((uint8_t *)&self->tiles)[i] = (uint8_t)default_tile; + } } self->inline_tiles = true; } else { - self->tiles = (uint8_t *)m_malloc(total_tiles); - for (uint32_t i = 0; i < total_tiles; i++) { - self->tiles[i] = default_tile; + if (use_uint16) { + uint16_t *tiles16 = (uint16_t *)m_malloc(total_tiles * sizeof(uint16_t)); + for (uint32_t i = 0; i < total_tiles; i++) { + tiles16[i] = default_tile; + } + self->tiles = tiles16; + } else { + uint8_t *tiles8 = (uint8_t *)m_malloc(total_tiles); + for (uint32_t i = 0; i < total_tiles; i++) { + tiles8[i] = (uint8_t)default_tile; + } + self->tiles = tiles8; } self->inline_tiles = false; } - self->bitmap_width_in_tiles = bitmap_width_in_tiles; - self->tiles_in_bitmap = bitmap_width_in_tiles * bitmap_height_in_tiles; + self->width_in_tiles = width; self->height_in_tiles = height; self->x = x; @@ -234,29 +258,42 @@ uint16_t common_hal_displayio_tilegrid_get_tile_height(displayio_tilegrid_t *sel return self->tile_height; } -uint8_t common_hal_displayio_tilegrid_get_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y) { - uint8_t *tiles = self->tiles; +uint16_t common_hal_displayio_tilegrid_get_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y) { + void *tiles = self->tiles; if (self->inline_tiles) { - tiles = (uint8_t *)&self->tiles; + tiles = &self->tiles; } if (tiles == NULL) { return 0; } - return tiles[y * self->width_in_tiles + x]; + + uint32_t index = y * self->width_in_tiles + x; + if (self->tiles_in_bitmap > 255) { + return ((uint16_t *)tiles)[index]; + } else { + return ((uint8_t *)tiles)[index]; + } } -void common_hal_displayio_tilegrid_set_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y, uint8_t tile_index) { +void common_hal_displayio_tilegrid_set_tile(displayio_tilegrid_t *self, uint16_t x, uint16_t y, uint16_t tile_index) { if (tile_index >= self->tiles_in_bitmap) { mp_raise_ValueError(MP_ERROR_TEXT("Tile index out of bounds")); } - uint8_t *tiles = self->tiles; + + void *tiles = self->tiles; if (self->inline_tiles) { - tiles = (uint8_t *)&self->tiles; + tiles = &self->tiles; } if (tiles == NULL) { return; } - tiles[y * self->width_in_tiles + x] = tile_index; + + uint32_t index = y * self->width_in_tiles + x; + if (self->tiles_in_bitmap > 255) { + ((uint16_t *)tiles)[index] = tile_index; + } else { + ((uint8_t *)tiles)[index] = (uint8_t)tile_index; + } displayio_area_t temp_area; displayio_area_t *tile_area; if (!self->partial_change) { @@ -284,21 +321,32 @@ void common_hal_displayio_tilegrid_set_tile(displayio_tilegrid_t *self, uint16_t self->partial_change = true; } -void common_hal_displayio_tilegrid_set_all_tiles(displayio_tilegrid_t *self, uint8_t tile_index) { +void common_hal_displayio_tilegrid_set_all_tiles(displayio_tilegrid_t *self, uint16_t tile_index) { if (tile_index >= self->tiles_in_bitmap) { mp_raise_ValueError(MP_ERROR_TEXT("Tile index out of bounds")); } - uint8_t *tiles = self->tiles; + + void *tiles = self->tiles; if (self->inline_tiles) { - tiles = (uint8_t *)&self->tiles; + tiles = &self->tiles; } if (tiles == NULL) { return; } - for (uint16_t x = 0; x < self->width_in_tiles; x++) { + if (self->tiles_in_bitmap > 255) { + uint16_t *tiles16 = (uint16_t *)tiles; for (uint16_t y = 0; y < self->height_in_tiles; y++) { - tiles[y * self->width_in_tiles + x] = tile_index; + for (uint16_t x = 0; x < self->width_in_tiles; x++) { + tiles16[y * self->width_in_tiles + x] = tile_index; + } + } + } else { + uint8_t *tiles8 = (uint8_t *)tiles; + for (uint16_t y = 0; y < self->height_in_tiles; y++) { + for (uint16_t x = 0; x < self->width_in_tiles; x++) { + tiles8[y * self->width_in_tiles + x] = (uint8_t)tile_index; + } } } @@ -368,9 +416,9 @@ bool displayio_tilegrid_fill_area(displayio_tilegrid_t *self, const _displayio_colorspace_t *colorspace, const displayio_area_t *area, uint32_t *mask, uint32_t *buffer) { // If no tiles are present we have no impact. - uint8_t *tiles = self->tiles; + void *tiles = self->tiles; if (self->inline_tiles) { - tiles = (uint8_t *)&self->tiles; + tiles = &self->tiles; } if (tiles == NULL) { return false; @@ -474,7 +522,12 @@ bool displayio_tilegrid_fill_area(displayio_tilegrid_t *self, uint16_t x_tile_index = (local_x / self->tile_width + self->top_left_x) % self->width_in_tiles; uint16_t y_tile_index = (local_y / self->tile_height + self->top_left_y) % self->height_in_tiles; uint16_t tile_location = y_tile_index * self->width_in_tiles + x_tile_index; - input_pixel.tile = tiles[tile_location]; + + if (self->tiles_in_bitmap > 255) { + input_pixel.tile = ((uint16_t *)tiles)[tile_location]; + } else { + input_pixel.tile = ((uint8_t *)tiles)[tile_location]; + } input_pixel.tile_x = (input_pixel.tile % self->bitmap_width_in_tiles) * self->tile_width + local_x % self->tile_width; input_pixel.tile_y = (input_pixel.tile / self->bitmap_width_in_tiles) * self->tile_height + local_y % self->tile_height; diff --git a/shared-module/displayio/TileGrid.h b/shared-module/displayio/TileGrid.h index 36d2c9a69b6cb..400ade5359827 100644 --- a/shared-module/displayio/TileGrid.h +++ b/shared-module/displayio/TileGrid.h @@ -30,7 +30,7 @@ typedef struct { uint16_t tile_height; uint16_t top_left_x; uint16_t top_left_y; - uint8_t *tiles; + void *tiles; // Can be either uint8_t* or uint16_t* depending on tiles_in_bitmap const displayio_buffer_transform_t *absolute_transform; displayio_area_t dirty_area; // Stored as a relative area until the refresh area is fetched. displayio_area_t previous_area; // Stored as an absolute area. diff --git a/shared-module/lvfontio/OnDiskFont.c b/shared-module/lvfontio/OnDiskFont.c new file mode 100644 index 0000000000000..0cf65301d2270 --- /dev/null +++ b/shared-module/lvfontio/OnDiskFont.c @@ -0,0 +1,843 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared-bindings/lvfontio/OnDiskFont.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/stream.h" +#include "py/objstr.h" +#include "py/gc.h" +#include "shared-bindings/displayio/Bitmap.h" +#include "extmod/vfs_fat.h" +#include "lib/oofatfs/ff.h" +#include "supervisor/shared/translate/translate.h" +#include "supervisor/port.h" +#include "supervisor/shared/serial.h" +#include "supervisor/filesystem.h" + +// Helper functions for memory allocation +static inline void *allocate_memory(lvfontio_ondiskfont_t *self, size_t size) { + void *ptr; + if (self->use_gc_allocator) { + ptr = m_malloc_maybe(size); + } else { + ptr = port_malloc(size, false); + } + if (ptr != NULL) { + return ptr; + } + common_hal_lvfontio_ondiskfont_deinit(self); + if (self->use_gc_allocator) { + m_malloc_fail(size); + } + return NULL; +} + +static inline void free_memory(lvfontio_ondiskfont_t *self, void *ptr) { + if (self->use_gc_allocator) { + m_free(ptr); + } else { + port_free(ptr); + } +} + +// Forward declarations for helper functions +static int16_t find_codepoint_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint); +static uint16_t find_free_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint); +static FRESULT read_bits(FIL *file, size_t num_bits, uint8_t *byte_val, uint8_t *remaining_bits, uint32_t *result); +static FRESULT read_glyph_dimensions(FIL *file, lvfontio_ondiskfont_t *self, uint32_t *advance_width, int32_t *bbox_x, int32_t *bbox_y, uint32_t *bbox_w, uint32_t *bbox_h, uint8_t *byte_val, uint8_t *remaining_bits); + +// Load font header data from file +static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max_slots) { + UINT bytes_read; + FRESULT res; + + // Start at the beginning of the file + res = f_lseek(file, 0); + if (res != FR_OK) { + return false; + } + + uint8_t buffer[8]; + bool found_head = false; + bool found_cmap = false; + bool found_loca = false; + bool found_glyf = false; + + + size_t current_position = 0; + + // Read sections until we find all the sections we need or reach end of file + while (true) { + // Read section size (4 bytes) + res = f_read(file, buffer, 4, &bytes_read); + if (res != FR_OK || bytes_read < 4) { + break; // Read error or end of file + } + + uint32_t section_size = buffer[0] | (buffer[1] << 8) | + (buffer[2] << 16) | (buffer[3] << 24); + + if (section_size == 0) { + break; // End of sections marker + } + + // Read section marker (4 bytes) + res = f_read(file, buffer, 4, &bytes_read); + if (res != FR_OK || bytes_read < 4) { + break; // Read error or unexpected end of file + } + + + // Make a null-terminated copy of the section marker for debug printing + char section_marker[5] = {0}; + memcpy(section_marker, buffer, 4); + + // Process different section types + if (memcmp(buffer, "head", 4) == 0) { + // Read head section data (35 bytes) + uint8_t head_buf[35]; + res = f_read(file, head_buf, 35, &bytes_read); + if (res != FR_OK || bytes_read < 35) { + break; + } + + // Skip version (4 bytes) and padding (1 byte) + // Parse font metrics at offset 6 + self->header.font_size = head_buf[6] | (head_buf[7] << 8); + self->header.ascent = head_buf[8] | (head_buf[9] << 8); + self->header.default_advance_width = head_buf[22] | (head_buf[23] << 8); + + // Parse format information + self->header.index_to_loc_format = head_buf[26]; + self->header.bits_per_pixel = head_buf[29]; + self->header.glyph_bbox_xy_bits = head_buf[30]; + self->header.glyph_bbox_wh_bits = head_buf[31]; + self->header.glyph_advance_bits = head_buf[32]; + + // Calculate derived values + self->header.glyph_header_bits = self->header.glyph_advance_bits + + 2 * self->header.glyph_bbox_xy_bits + + 2 * self->header.glyph_bbox_wh_bits; + self->header.glyph_header_bytes = (self->header.glyph_header_bits + 7) / 8; + + found_head = true; + } else if (memcmp(buffer, "cmap", 4) == 0) { + // Read subtable count + uint8_t cmap_header[4]; + res = f_read(file, cmap_header, 4, &bytes_read); + if (res != FR_OK || bytes_read < 4) { + break; + } + + uint32_t subtable_count = cmap_header[0] | (cmap_header[1] << 8) | + (cmap_header[2] << 16) | (cmap_header[3] << 24); + + // Allocate memory for cmap ranges + self->cmap_range_count = subtable_count; + self->cmap_ranges = allocate_memory(self, sizeof(lvfontio_cmap_range_t) * subtable_count); + if (self->cmap_ranges == NULL) { + return false; + } + + // Read each subtable + for (uint16_t i = 0; i < subtable_count; i++) { + uint8_t subtable_buf[16]; + res = f_read(file, subtable_buf, 16, &bytes_read); + if (res != FR_OK || bytes_read < 16) { + break; + } + + // Read data_offset (4 bytes) + uint32_t data_offset = subtable_buf[0] | (subtable_buf[1] << 8) | + (subtable_buf[2] << 16) | (subtable_buf[3] << 24); + + // Read range_start, range_length, glyph_offset + uint32_t range_start = subtable_buf[4] | (subtable_buf[5] << 8) | + (subtable_buf[6] << 16) | (subtable_buf[7] << 24); + uint16_t range_length = subtable_buf[8] | (subtable_buf[9] << 8); + uint16_t glyph_offset = subtable_buf[10] | (subtable_buf[11] << 8); + uint16_t entries_count = subtable_buf[12] | (subtable_buf[13] << 8); + + // Get format type (0=sparse mapping, 1=range mapping, 2=range to range, 3=direct mapping) + uint8_t format_type = subtable_buf[14]; + // Check for supported format types (0, 2, and 3) + if (format_type != 0 && format_type != 2 && format_type != 3) { + continue; + } + + // Store the range information + self->cmap_ranges[i].range_start = range_start; + self->cmap_ranges[i].range_end = range_start + range_length; + self->cmap_ranges[i].glyph_offset = glyph_offset; + self->cmap_ranges[i].format_type = format_type; + self->cmap_ranges[i].data_offset = current_position + data_offset; + self->cmap_ranges[i].entries_count = entries_count; + } + + found_cmap = true; + } else if (memcmp(buffer, "loca", 4) == 0) { + // Read max_cid + uint8_t loca_header[4]; + res = f_read(file, loca_header, 4, &bytes_read); + if (res != FR_OK || bytes_read < 4) { + break; + } + + // Store max_cid value + self->max_cid = loca_header[0] | (loca_header[1] << 8) | + (loca_header[2] << 16) | (loca_header[3] << 24); + + // Store location of the loca table offset data + self->loca_table_offset = current_position + 12; + + found_loca = true; + } else if (memcmp(buffer, "glyf", 4) == 0) { + // Store start of glyf table + self->glyf_table_offset = current_position; + size_t advances[2] = {0, 0}; + size_t advance_count[2] = {0, 0}; + + if (self->header.default_advance_width != 0) { + advances[0] = self->header.default_advance_width; + } + + // Set the default advance width based on the first character in the + // file. + size_t cid = 0; + while (cid < self->max_cid - 1) { + // Read glyph header fields + uint32_t glyph_advance; + int32_t bbox_x, bbox_y; + uint32_t bbox_w, bbox_h; + + uint8_t byte_val = 0; + uint8_t remaining_bits = 0; + + // Use the helper function to read glyph dimensions + read_glyph_dimensions(file, self, &glyph_advance, &bbox_x, &bbox_y, &bbox_w, &bbox_h, &byte_val, &remaining_bits); + + // Throw away the bitmap bits. + read_bits(file, self->header.bits_per_pixel * bbox_w * bbox_h, &byte_val, &remaining_bits, NULL); + if (advances[0] == glyph_advance) { + advance_count[0]++; + } else if (advances[1] == glyph_advance) { + advance_count[1]++; + } else if (advance_count[0] == 0) { + advances[0] = glyph_advance; + advance_count[0] = 1; + } else if (advance_count[1] == 0) { + advances[1] = glyph_advance; + advance_count[1] = 1; + } else { + break; + } + cid++; + } + + if (self->header.default_advance_width == 0) { + if (advance_count[1] == 0) { + self->header.default_advance_width = advances[0]; + *max_slots = advance_count[0]; + } else { + if (advances[0] > advances[1]) { + self->header.default_advance_width = advances[0] / 2; + *max_slots = advance_count[0] * 2 + advance_count[1]; + } else { + self->header.default_advance_width = advances[1] / 2; + *max_slots = advance_count[1] * 2 + advance_count[0]; + } + } + } + + + found_glyf = true; + } + + current_position += section_size; + + // Skip to the end of the section + res = f_lseek(file, current_position); + if (res != FR_OK) { + break; + } + + // If we found all needed sections, we can stop + if (found_head && found_cmap && found_loca && found_glyf) { + break; + } + } + + // Check if we found all required sections + if (!found_head || !found_cmap || !found_loca || !found_glyf) { + return false; + } + + return true; +} + +// Get character ID (glyph index) for a codepoint +static int32_t get_char_id(lvfontio_ondiskfont_t *self, uint32_t codepoint) { + // Find codepoint in cmap ranges + for (uint16_t i = 0; i < self->cmap_range_count; i++) { + // Check if codepoint is in range for this subtable + if (codepoint >= self->cmap_ranges[i].range_start && + codepoint < self->cmap_ranges[i].range_end) { + + // Handle according to format type + switch (self->cmap_ranges[i].format_type) { + case 0: { // Sparse mapping - need to look up in a sparse table + if (!self->file_is_open) { + return -1; + } + + // Calculate the relative position within the range + uint32_t idx = codepoint - self->cmap_ranges[i].range_start; + + if (idx >= self->cmap_ranges[i].entries_count) { + return -1; + } + + // Calculate the absolute data position in the file + uint32_t data_pos = self->cmap_ranges[i].data_offset + idx; // 1 byte per entry + FRESULT res = f_lseek(&self->file, data_pos); + if (res != FR_OK) { + return -1; + } + + // Read the glyph ID (1 byte) + uint8_t glyph_id; + UINT bytes_read; + res = f_read(&self->file, &glyph_id, 1, &bytes_read); + + if (res != FR_OK || bytes_read < 1) { + return -1; + } + + + return self->cmap_ranges[i].glyph_offset + glyph_id; + } + + case 2: // Range to range - calculate based on offset within range + uint16_t idx = codepoint - self->cmap_ranges[i].range_start; + uint16_t glyph_id = self->cmap_ranges[i].glyph_offset + idx; + return glyph_id; + + case 3: { // Direct mapping - need to look up in the table + if (!self->file_is_open) { + return -1; + } + + FRESULT res; + res = f_lseek(&self->file, self->cmap_ranges[i].data_offset); + if (res != FR_OK) { + return -1; + } + uint16_t codepoint_delta = codepoint - self->cmap_ranges[i].range_start; + + for (size_t j = 0; j < self->cmap_ranges[i].entries_count; j++) { + // Read code point at the index + uint16_t candidate_codepoint_delta; + res = f_read(&self->file, &candidate_codepoint_delta, 2, NULL); + if (res != FR_OK) { + return -1; + } + + if (candidate_codepoint_delta == codepoint_delta) { + return self->cmap_ranges[i].glyph_offset + j; + } + } + return -1; + } + + default: + return -1; + } + } + } + + return -1; // Not found +} + +// Load glyph bitmap data into a slot +// This function assumes the file is already open and positioned after reading the glyph dimensions +static bool load_glyph_bitmap(FIL *file, lvfontio_ondiskfont_t *self, uint32_t codepoint, uint16_t slot, + uint32_t glyph_advance, int32_t bbox_x, int32_t bbox_y, uint32_t bbox_w, uint32_t bbox_h, + uint8_t *byte_val, uint8_t *remaining_bits) { + // Store codepoint at slot + self->codepoints[slot] = codepoint; + self->reference_counts[slot] = 1; + + // Read bitmap data pixel by pixel + uint16_t x_offset = slot * self->header.default_advance_width; + uint16_t y_offset = self->header.ascent - bbox_y - bbox_h; + for (uint16_t y = 0; y < bbox_h; y++) { + for (uint16_t x = 0; x < bbox_w; x++) { + uint32_t pixel_value; + FRESULT res = read_bits(file, self->header.bits_per_pixel, byte_val, remaining_bits, &pixel_value); + if (res != FR_OK) { + return false; + } + + // Adjust for bbox position within the glyph bounding box + int16_t bitmap_x = x_offset + x + bbox_x; + int16_t bitmap_y = y_offset + y; + + // Make sure we're in bounds + if (bitmap_x >= 0 && + bitmap_x < self->header.default_advance_width * self->max_glyphs && + bitmap_y >= 0 && + bitmap_y < self->header.font_size) { + common_hal_displayio_bitmap_set_pixel( + self->bitmap, + bitmap_x, + bitmap_y, + pixel_value + ); + } + } + } + + return true; +} + +// Constructor +void common_hal_lvfontio_ondiskfont_construct(lvfontio_ondiskfont_t *self, + const char *file_path, + uint16_t max_glyphs, + bool use_gc_allocator) { + + // Store the allocation mode + self->use_gc_allocator = use_gc_allocator; + // Store parameters + self->file_path = file_path; // Store the provided path string directly + self->max_glyphs = max_glyphs; + self->cmap_ranges = NULL; + self->file_is_open = false; + + // Determine which filesystem to use based on the path + const char *path_under_mount; + fs_user_mount_t *vfs = filesystem_for_path(file_path, &path_under_mount); + + if (vfs == NULL) { + if (self->use_gc_allocator) { + mp_raise_ValueError(MP_ERROR_TEXT("File not found")); + } + return; + } + + // Open the file and keep it open for the lifetime of the object + FRESULT res = f_open(&vfs->fatfs, &self->file, path_under_mount, FA_READ); + + if (res != FR_OK) { + if (self->use_gc_allocator) { + mp_raise_ValueError(MP_ERROR_TEXT("File not found")); + } + return; + } + + self->file_is_open = true; + + // Load font headers + size_t max_slots; + if (!load_font_header(self, &self->file, &max_slots)) { + f_close(&self->file); + self->file_is_open = false; + if (self->use_gc_allocator) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_file); + } + return; + } + // Cap the number of slots to the number of slots needed by the font. That way + // small font files don't need a bunch of extra cache space. + max_glyphs = MIN(max_glyphs, max_slots); + + // Allocate codepoints array. allocate_memory will raise an exception if + // allocation fails and the VM is active. + self->codepoints = allocate_memory(self, sizeof(uint32_t) * max_glyphs); + if (self->codepoints == NULL) { + return; + } + + // Initialize codepoints to invalid + for (uint16_t i = 0; i < max_glyphs; i++) { + self->codepoints[i] = LVFONTIO_INVALID_CODEPOINT; + } + + // Allocate reference counts + self->reference_counts = allocate_memory(self, sizeof(uint16_t) * max_glyphs); + if (self->reference_counts == NULL) { + return; + } + + // Initialize reference counts to 0 + memset(self->reference_counts, 0, sizeof(uint16_t) * max_glyphs); + + self->half_width_px = self->header.default_advance_width; + + // Create bitmap for glyph cache + displayio_bitmap_t *bitmap = allocate_memory(self, sizeof(displayio_bitmap_t)); + bitmap->base.type = &displayio_bitmap_type; + if (bitmap == NULL) { + return; + } + + // Calculate bitmap stride + uint32_t bits_per_pixel = 1 << self->header.bits_per_pixel; + uint32_t width = self->header.default_advance_width * max_glyphs; + uint32_t row_width = width * bits_per_pixel; + uint16_t stride = (row_width + 31) / 32; // Align to uint32_t (32 bits) + + // Allocate buffer for bitmap data + uint32_t buffer_size = stride * self->header.font_size * sizeof(uint32_t); + uint32_t *bitmap_buffer = allocate_memory(self, buffer_size); + if (bitmap_buffer == NULL) { + return; + } + + // Zero out bitmap buffer + memset(bitmap_buffer, 0, buffer_size); + + // Construct bitmap with allocated buffer + common_hal_displayio_bitmap_construct_from_buffer(bitmap, + self->header.default_advance_width * max_glyphs, + self->header.font_size, + 1 << self->header.bits_per_pixel, + bitmap_buffer, + false); + self->bitmap = bitmap; +} + +void common_hal_lvfontio_ondiskfont_deinit(lvfontio_ondiskfont_t *self) { + if (!self->file_is_open) { + return; + } + + if (self->bitmap != NULL) { + common_hal_displayio_bitmap_deinit(self->bitmap); + self->bitmap = NULL; + } + + if (self->codepoints != NULL) { + free_memory(self, self->codepoints); + self->codepoints = NULL; + } + + if (self->reference_counts != NULL) { + free_memory(self, self->reference_counts); + self->reference_counts = NULL; + } + + + + if (self->cmap_ranges != NULL) { + free_memory(self, self->cmap_ranges); + self->cmap_ranges = NULL; + } + + f_close(&self->file); + self->file_is_open = false; +} + +bool common_hal_lvfontio_ondiskfont_deinited(lvfontio_ondiskfont_t *self) { + return !self->file_is_open; +} + +mp_obj_t common_hal_lvfontio_ondiskfont_get_bitmap(const lvfontio_ondiskfont_t *self) { + return MP_OBJ_FROM_PTR(self->bitmap); +} + +mp_obj_t common_hal_lvfontio_ondiskfont_get_bounding_box(const lvfontio_ondiskfont_t *self) { + mp_obj_t bbox[2]; + bbox[0] = MP_OBJ_NEW_SMALL_INT(self->header.default_advance_width); + bbox[1] = MP_OBJ_NEW_SMALL_INT(self->header.font_size); + return mp_obj_new_tuple(2, bbox); +} + +void common_hal_lvfontio_ondiskfont_get_dimensions(const lvfontio_ondiskfont_t *self, + uint16_t *width, uint16_t *height) { + if (width != NULL) { + *width = self->header.default_advance_width; + } + if (height != NULL) { + *height = self->header.font_size; + } +} + +int16_t common_hal_lvfontio_ondiskfont_cache_glyph(lvfontio_ondiskfont_t *self, uint32_t codepoint, bool *is_full_width) { + // Check if already cached + int16_t existing_slot = find_codepoint_slot(self, codepoint); + if (existing_slot >= 0) { + // Glyph is already cached, increment reference count + self->reference_counts[existing_slot]++; + + // Check if this is a full-width character by looking for a second slot + // with the same codepoint right after this one + if (is_full_width != NULL) { + if (existing_slot + 1 < self->max_glyphs && + self->codepoints[existing_slot + 1] == codepoint) { + *is_full_width = true; + } else { + *is_full_width = false; + } + } + + return existing_slot; + } + + // First check if the glyph is full-width before allocating slots + // This way we know if we need one or two slots before committing + bool is_full_width_glyph = false; + + // Check if file is already open + if (!self->file_is_open) { + + return -1; + } + + // Find character ID from codepoint + int32_t char_id = get_char_id(self, codepoint); + if (char_id < 0 || (uint32_t)char_id >= self->max_cid) { + return -1; // Invalid character + } + + // Get glyph offset from location table + uint32_t glyph_offset = 0; + uint32_t loca_offset = self->loca_table_offset + char_id * + (self->header.index_to_loc_format == 1 ? 4 : 2); + + FRESULT res = f_lseek(&self->file, loca_offset); + if (res != FR_OK) { + return -1; + } + + UINT bytes_read; + if (self->header.index_to_loc_format == 1) { + // 4-byte offset + uint8_t offset_buf[4]; + res = f_read(&self->file, offset_buf, 4, &bytes_read); + if (res != FR_OK || bytes_read < 4) { + return -1; + } + glyph_offset = offset_buf[0] | (offset_buf[1] << 8) | + (offset_buf[2] << 16) | (offset_buf[3] << 24); + } else { + // 2-byte offset + uint8_t offset_buf[2]; + res = f_read(&self->file, offset_buf, 2, &bytes_read); + if (res != FR_OK || bytes_read < 2) { + return -1; + } + glyph_offset = offset_buf[0] | (offset_buf[1] << 8); + } + // Seek to glyph data + res = f_lseek(&self->file, self->glyf_table_offset + glyph_offset); + if (res != FR_OK) { + return -1; + } + + // Read glyph header fields to determine width + uint32_t glyph_advance; + int32_t bbox_x, bbox_y; + uint32_t bbox_w, bbox_h; + + // Initialize bit reading state + uint8_t byte_val = 0; + uint8_t remaining_bits = 0; + + // Use the helper function to read glyph dimensions + res = read_glyph_dimensions(&self->file, self, &glyph_advance, &bbox_x, &bbox_y, &bbox_w, &bbox_h, &byte_val, &remaining_bits); + if (res != FR_OK) { + return -1; + } + + // Check if the glyph is full-width based on its advance width + // Full-width characters typically have an advance width close to or greater than the font height + is_full_width_glyph = glyph_advance > self->half_width_px; + + // Now we know if we need one or two slots + uint16_t slots_needed = is_full_width_glyph ? 2 : 1; + + // Find an appropriate slot (or consecutive slots for full-width) + uint16_t slot = UINT16_MAX; + + if (slots_needed == 1) { + // For regular width, find a free slot starting at codepoint's position + slot = find_free_slot(self, codepoint); + } else { + // For full-width, find two consecutive free slots + for (uint16_t i = 0; i < self->max_glyphs - 1; i++) { + if (self->codepoints[i] == LVFONTIO_INVALID_CODEPOINT && + self->reference_counts[i] == 0 && + self->codepoints[i + 1] == LVFONTIO_INVALID_CODEPOINT && + self->reference_counts[i + 1] == 0) { + slot = i; + break; + } + } + } + + // Check if we found appropriate slot(s) + if (slot == UINT16_MAX) { + return -1; // No slots available + } + + // Load glyph into the slot + if (!load_glyph_bitmap(&self->file, self, codepoint, slot, glyph_advance, + bbox_x, bbox_y, bbox_w, bbox_h, &byte_val, &remaining_bits)) { + return -1; // Failed to load glyph + } + + // For full-width characters, mark both slots with the same codepoint + if (is_full_width_glyph && slot + 1 < self->max_glyphs) { + self->codepoints[slot + 1] = codepoint; + self->reference_counts[slot + 1] = 1; + } + + if (is_full_width != NULL) { + *is_full_width = is_full_width_glyph; + } + + return slot; +} + +void common_hal_lvfontio_ondiskfont_release_glyph(lvfontio_ondiskfont_t *self, uint32_t slot) { + if (slot >= self->max_glyphs) { + return; + } + + if (self->reference_counts[slot] > 0) { + self->reference_counts[slot]--; + } +} + +static int16_t find_codepoint_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint) { + size_t offset = codepoint % self->max_glyphs; + for (uint16_t i = 0; i < self->max_glyphs; i++) { + int16_t slot = (i + offset) % self->max_glyphs; + if (self->codepoints[slot] == codepoint) { + return slot; + } + } + return -1; +} + +static uint16_t find_free_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint) { + size_t offset = codepoint % self->max_glyphs; + + // First look for completely unused slots, starting at the offset + for (uint16_t i = 0; i < self->max_glyphs; i++) { + int16_t slot = (i + offset) % self->max_glyphs; + if (self->codepoints[slot] == LVFONTIO_INVALID_CODEPOINT && self->reference_counts[slot] == 0) { + return slot; + } + } + + // If none found, look for slots with zero reference count, starting at the offset + for (uint16_t i = 0; i < self->max_glyphs; i++) { + int16_t slot = (i + offset) % self->max_glyphs; + if (self->reference_counts[slot] == 0) { + return slot; + } + } + + // No slots available + return UINT16_MAX; +} + +static FRESULT read_glyph_dimensions(FIL *file, lvfontio_ondiskfont_t *self, + uint32_t *advance_width, int32_t *bbox_x, int32_t *bbox_y, + uint32_t *bbox_w, uint32_t *bbox_h, + uint8_t *byte_val, uint8_t *remaining_bits) { + FRESULT res; + uint32_t temp_value; + + // Read glyph_advance + res = read_bits(file, self->header.glyph_advance_bits, byte_val, remaining_bits, &temp_value); + if (res != FR_OK) { + return res; + } + *advance_width = temp_value; + + // Read bbox_x (signed) + res = read_bits(file, self->header.glyph_bbox_xy_bits, byte_val, remaining_bits, &temp_value); + if (res != FR_OK) { + return res; + } + // Convert to signed value if needed + if (temp_value & (1 << (self->header.glyph_bbox_xy_bits - 1))) { + *bbox_x = temp_value - (1 << self->header.glyph_bbox_xy_bits); + } else { + *bbox_x = temp_value; + } + + // Read bbox_y (signed) + res = read_bits(file, self->header.glyph_bbox_xy_bits, byte_val, remaining_bits, &temp_value); + if (res != FR_OK) { + return res; + } + // Convert to signed value if needed + if (temp_value & (1 << (self->header.glyph_bbox_xy_bits - 1))) { + *bbox_y = temp_value - (1 << self->header.glyph_bbox_xy_bits); + } else { + *bbox_y = temp_value; + } + + // Read bbox_w + res = read_bits(file, self->header.glyph_bbox_wh_bits, byte_val, remaining_bits, &temp_value); + if (res != FR_OK) { + return res; + } + *bbox_w = temp_value; + + // Read bbox_h + res = read_bits(file, self->header.glyph_bbox_wh_bits, byte_val, remaining_bits, &temp_value); + if (res != FR_OK) { + return res; + } + *bbox_h = temp_value; + + return FR_OK; +} + +static FRESULT read_bits(FIL *file, size_t num_bits, uint8_t *byte_val, uint8_t *remaining_bits, uint32_t *result) { + FRESULT res = FR_OK; + UINT bytes_read; + + uint32_t value = 0; + // Bits will be lost when num_bits > 32. However, this is good for skipping bits. + size_t bits_needed = num_bits; + + while (bits_needed > 0) { + // If no bits remaining, read a new byte + if (*remaining_bits == 0) { + res = f_read(file, byte_val, 1, &bytes_read); + if (res != FR_OK || bytes_read < 1) { + return FR_DISK_ERR; + } + *remaining_bits = 8; + } + + // Calculate how many bits to take from current byte + uint8_t bits_to_take = (*remaining_bits < bits_needed) ? *remaining_bits : bits_needed; + value = (value << bits_to_take) | (*byte_val >> (8 - bits_to_take)); + + // Update state + *remaining_bits -= bits_to_take; + bits_needed -= bits_to_take; + + // Shift byte for next read + *byte_val <<= bits_to_take; + *byte_val &= 0xFF; + } + + if (result != NULL) { + *result = value; + } + return FR_OK; +} diff --git a/shared-module/lvfontio/OnDiskFont.h b/shared-module/lvfontio/OnDiskFont.h new file mode 100644 index 0000000000000..db68126e9f459 --- /dev/null +++ b/shared-module/lvfontio/OnDiskFont.h @@ -0,0 +1,75 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" +#include "shared-module/displayio/Bitmap.h" + +#include "lib/oofatfs/ff.h" + +#define LVFONTIO_INVALID_CODEPOINT 0xFFFFFFFF + +// LV Font header information +typedef struct { + // Font size and metrics + uint16_t font_size; + uint16_t ascent; + uint16_t default_advance_width; + + // Encoding formats + uint8_t index_to_loc_format; + uint8_t bits_per_pixel; + uint8_t glyph_bbox_xy_bits; + uint8_t glyph_bbox_wh_bits; + uint8_t glyph_advance_bits; + + // Calculated values + uint8_t glyph_header_bits; + uint8_t glyph_header_bytes; +} lvfontio_header_t; + +// Mapping of codepoint ranges to glyph IDs +typedef struct { + uint32_t range_start; // Start of codepoint range + uint32_t range_end; // End of codepoint range (exclusive) + uint16_t glyph_offset; // Offset to apply to codepoint + uint8_t format_type; // Format type: 0=sparse mapping, 2=range to range, 3=direct mapping + uint16_t entries_count; // Number of entries in sparse data + uint32_t data_offset; // File offset to the cmap data +} lvfontio_cmap_range_t; + +typedef struct { + mp_obj_base_t base; + // Bitmap containing cached glyphs + displayio_bitmap_t *bitmap; + // Source of font file path (either a const char* or a copied string) + const char *file_path; + // Array mapping glyph indices to codepoints + uint32_t *codepoints; + // Array of reference counts for each glyph slot + uint16_t *reference_counts; // Use uint16_t to handle higher reference counts + // Maximum number of glyphs to cache at once + uint16_t max_glyphs; + // Flag indicating whether to use m_malloc (true) or port_malloc (false) + bool use_gc_allocator; + uint8_t half_width_px; + + FIL file; + bool file_is_open; + + // Font metrics information loaded from file + lvfontio_header_t header; + + // CMAP information + lvfontio_cmap_range_t *cmap_ranges; + uint16_t cmap_range_count; + + // Offsets for tables in the file + uint32_t loca_table_offset; + uint32_t glyf_table_offset; + uint32_t max_cid; +} lvfontio_ondiskfont_t; diff --git a/shared-module/lvfontio/__init__.c b/shared-module/lvfontio/__init__.c new file mode 100644 index 0000000000000..d0eee6d0a521a --- /dev/null +++ b/shared-module/lvfontio/__init__.c @@ -0,0 +1,5 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT diff --git a/shared-module/terminalio/Terminal.c b/shared-module/terminalio/Terminal.c index 2828d0d3800c5..02bcffb30c5f2 100644 --- a/shared-module/terminalio/Terminal.c +++ b/shared-module/terminalio/Terminal.c @@ -10,20 +10,150 @@ #include "shared-bindings/displayio/TileGrid.h" #include "shared-bindings/displayio/Palette.h" #include "shared-bindings/terminalio/Terminal.h" +#include "shared-bindings/fontio/BuiltinFont.h" +#if CIRCUITPY_LVFONTIO +#include "shared-bindings/lvfontio/OnDiskFont.h" +#endif #if CIRCUITPY_STATUS_BAR #include "shared-bindings/supervisor/__init__.h" #include "shared-bindings/supervisor/StatusBar.h" #endif +#include "supervisor/shared/serial.h" + +uint16_t terminalio_terminal_get_glyph_index(mp_obj_t font, mp_uint_t codepoint, bool *is_full_width) { + if (is_full_width != NULL) { + *is_full_width = false; // Default to not full width + } + + #if CIRCUITPY_LVFONTIO + if (mp_obj_is_type(font, &lvfontio_ondiskfont_type)) { + // For LV fonts, we need to cache the glyph first + lvfontio_ondiskfont_t *lv_font = MP_OBJ_TO_PTR(font); + bool full_width = false; + int16_t slot = common_hal_lvfontio_ondiskfont_cache_glyph(lv_font, codepoint, &full_width); + + if (is_full_width != NULL) { + *is_full_width = full_width; + } + + if (slot == -1) { + // Not found or couldn't cache + return 0xffff; + } + return (uint16_t)slot; + } + #endif + + #if CIRCUITPY_FONTIO + if (mp_obj_is_type(font, &fontio_builtinfont_type)) { + // Use the standard fontio function + fontio_builtinfont_t *fontio_font = MP_OBJ_TO_PTR(font); + uint8_t index = fontio_builtinfont_get_glyph_index(fontio_font, codepoint); + if (index == 0xff) { + return 0xffff; + } + return index; + } + #endif + + // Unsupported font type + return 0xffff; +} + +static void wrap_cursor(uint16_t width, uint16_t height, uint16_t *cursor_x, uint16_t *cursor_y) { + if (*cursor_x >= width) { + *cursor_y = *cursor_y + 1; + *cursor_x %= width; + } + if (*cursor_y >= height) { + *cursor_y %= height; + } +} + +static void release_current_glyph(displayio_tilegrid_t *tilegrid, mp_obj_t font, uint16_t x, uint16_t y) { + #if CIRCUITPY_LVFONTIO + if (!mp_obj_is_type(font, &lvfontio_ondiskfont_type)) { + return; + } + uint16_t current_tile = common_hal_displayio_tilegrid_get_tile(tilegrid, x, y); + if (current_tile == 0) { + } + common_hal_lvfontio_ondiskfont_release_glyph(MP_OBJ_TO_PTR(font), current_tile); + #endif +} + +static void terminalio_terminal_set_tile(terminalio_terminal_obj_t *self, bool status_bar, mp_uint_t character, bool release_glyphs) { + displayio_tilegrid_t *tilegrid = self->scroll_area; + uint16_t *x = &self->cursor_x; + uint16_t *y = &self->cursor_y; + uint16_t w = self->scroll_area->width_in_tiles; + uint16_t h = self->scroll_area->height_in_tiles; + if (status_bar) { + tilegrid = self->status_bar; + x = &self->status_x; + y = &self->status_y; + w = self->status_bar->width_in_tiles; + h = self->status_bar->height_in_tiles; + } + if (release_glyphs) { + release_current_glyph(tilegrid, self->font, *x, *y); + } + bool is_full_width; + uint16_t new_tile = terminalio_terminal_get_glyph_index(self->font, character, &is_full_width); + if (new_tile == 0xffff) { + // Missing glyph. + return; + } + // If there is only half width left, then fill it with a space and wrap to the next line. + if (is_full_width && *x == w - 1) { + uint16_t space = terminalio_terminal_get_glyph_index(self->font, ' ', NULL); + common_hal_displayio_tilegrid_set_tile(tilegrid, *x, *y, space); + *x = *x + 1; + wrap_cursor(w, h, x, y); + if (release_glyphs) { + release_current_glyph(tilegrid, self->font, *x, *y); + } + } + common_hal_displayio_tilegrid_set_tile(tilegrid, *x, *y, new_tile); + *x = *x + 1; + wrap_cursor(w, h, x, y); + if (is_full_width) { + if (release_glyphs) { + release_current_glyph(tilegrid, self->font, *x, *y); + } + common_hal_displayio_tilegrid_set_tile(tilegrid, *x, *y, new_tile + 1); + *x = *x + 1; + wrap_cursor(w, h, x, y); + } +} + +// Helper function to set all tiles in a tilegrid with optional glyph release +static void terminalio_terminal_set_all_tiles(terminalio_terminal_obj_t *self, bool status_bar, mp_uint_t character, bool release_glyphs) { + uint16_t *x = &self->cursor_x; + uint16_t *y = &self->cursor_y; + if (status_bar) { + x = &self->status_x; + y = &self->status_y; + } + *x = 0; + *y = 0; + terminalio_terminal_set_tile(self, status_bar, character, release_glyphs); + while (*x != 0 || *y != 0) { + terminalio_terminal_set_tile(self, status_bar, character, release_glyphs); + } +} + void terminalio_terminal_clear_status_bar(terminalio_terminal_obj_t *self) { if (self->status_bar) { - common_hal_displayio_tilegrid_set_all_tiles(self->status_bar, 0); + terminalio_terminal_set_all_tiles(self, true, ' ', true); } } + void common_hal_terminalio_terminal_construct(terminalio_terminal_obj_t *self, - displayio_tilegrid_t *scroll_area, const fontio_builtinfont_t *font, + displayio_tilegrid_t *scroll_area, mp_obj_t font, displayio_tilegrid_t *status_bar) { self->cursor_x = 0; self->cursor_y = 0; @@ -35,9 +165,9 @@ void common_hal_terminalio_terminal_construct(terminalio_terminal_obj_t *self, self->first_row = 0; self->vt_scroll_top = 0; self->vt_scroll_end = self->scroll_area->height_in_tiles - 1; - common_hal_displayio_tilegrid_set_all_tiles(self->scroll_area, 0); + terminalio_terminal_set_all_tiles(self, false, ' ', false); if (self->status_bar) { - common_hal_displayio_tilegrid_set_all_tiles(self->status_bar, 0); + terminalio_terminal_set_all_tiles(self, true, ' ', false); } common_hal_displayio_tilegrid_set_top_left(self->scroll_area, 0, 1); @@ -85,29 +215,16 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con self->osc_command == 0 && self->status_bar != NULL && self->status_y < self->status_bar->height_in_tiles) { - uint8_t tile_index = fontio_builtinfont_get_glyph_index(self->font, c); - if (tile_index != 0xff) { - // Clear the tile grid before we start putting new info. - if (self->status_x == 0 && self->status_y == 0) { - common_hal_displayio_tilegrid_set_all_tiles(self->status_bar, 0); - } - common_hal_displayio_tilegrid_set_tile(self->status_bar, self->status_x, self->status_y, tile_index); - self->status_x++; - if (self->status_x >= self->status_bar->width_in_tiles) { - self->status_y++; - self->status_x %= self->status_bar->width_in_tiles; - } + // Clear the tile grid before we start putting new info. + if (self->status_x == 0 && self->status_y == 0) { + terminalio_terminal_set_all_tiles(self, true, ' ', true); } + terminalio_terminal_set_tile(self, true, c, true); } continue; } - // Always handle ASCII. - if (c < 128) { - if (c >= 0x20 && c <= 0x7e) { - uint8_t tile_index = fontio_builtinfont_get_glyph_index(self->font, c); - common_hal_displayio_tilegrid_set_tile(self->scroll_area, self->cursor_x, self->cursor_y, tile_index); - self->cursor_x++; - } else if (c == '\r') { + if (c < 0x20) { + if (c == '\r') { self->cursor_x = 0; } else if (c == '\n') { self->cursor_y++; @@ -162,6 +279,8 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con #endif } else { if (c == 'K') { + int16_t original_cursor_x = self->cursor_x; + int16_t original_cursor_y = self->cursor_y; int16_t clr_start = self->cursor_x; int16_t clr_end = self->scroll_area->width_in_tiles; #if CIRCUITPY_TERMINALIO_VT100 @@ -171,11 +290,14 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con } else if (vt_args[0] == 2) { clr_start = 0; } + self->cursor_x = clr_start; #endif // Clear the (start/rest/all) of the line. for (uint16_t k = clr_start; k < clr_end; k++) { - common_hal_displayio_tilegrid_set_tile(self->scroll_area, k, self->cursor_y, 0); + terminalio_terminal_set_tile(self, false, ' ', true); } + self->cursor_x = original_cursor_x; + self->cursor_y = original_cursor_y; } else if (c == 'D') { if (vt_args[0] > self->cursor_x) { self->cursor_x = 0; @@ -186,7 +308,7 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con if (vt_args[0] == 2) { common_hal_displayio_tilegrid_set_top_left(self->scroll_area, 0, 0); self->cursor_x = self->cursor_y = start_y = 0; - common_hal_displayio_tilegrid_set_all_tiles(self->scroll_area, 0); + terminalio_terminal_set_all_tiles(self, false, ' ', true); } } else if (c == 'H') { if (vt_args[0] > 0) { @@ -240,16 +362,20 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con self->cursor_y = self->scroll_area->height_in_tiles - 1; } } else { - if (self->vt_scroll_top != 0 || self->vt_scroll_end != self->scroll_area->height_in_tiles) { + if (self->vt_scroll_top != 0 || self->vt_scroll_end != self->scroll_area->height_in_tiles - 1) { // Scroll range defined, manually move tiles to perform scroll for (int16_t irow = self->vt_scroll_end - 1; irow >= self->vt_scroll_top; irow--) { for (int16_t icol = 0; icol < self->scroll_area->width_in_tiles; icol++) { common_hal_displayio_tilegrid_set_tile(self->scroll_area, icol, SCRNMOD(irow + 1), common_hal_displayio_tilegrid_get_tile(self->scroll_area, icol, SCRNMOD(irow))); } } + self->cursor_x = 0; + int16_t old_y = self->cursor_y; + // Fill the row with spaces. for (int16_t icol = 0; icol < self->scroll_area->width_in_tiles; icol++) { - common_hal_displayio_tilegrid_set_tile(self->scroll_area, icol, self->cursor_y, 0); + terminalio_terminal_set_tile(self, false, ' ', true); } + self->cursor_y = old_y; } else { // Full screen scroll, just set new top_y pointer and clear row if (self->cursor_y > 0) { @@ -257,8 +383,12 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con } else { common_hal_displayio_tilegrid_set_top_left(self->scroll_area, 0, self->scroll_area->height_in_tiles - 1); } - for (uint16_t icol = 0; icol < self->scroll_area->width_in_tiles; icol++) { - common_hal_displayio_tilegrid_set_tile(self->scroll_area, icol, self->scroll_area->top_left_y, 0); + + self->cursor_x = 0; + self->cursor_y = self->scroll_area->top_left_y; + // Fill the row with spaces. + for (int16_t icol = 0; icol < self->scroll_area->width_in_tiles; icol++) { + terminalio_terminal_set_tile(self, false, ' ', true); } self->cursor_y = self->scroll_area->top_left_y; } @@ -277,12 +407,7 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con } } } else { - uint8_t tile_index = fontio_builtinfont_get_glyph_index(self->font, c); - if (tile_index != 0xff) { - common_hal_displayio_tilegrid_set_tile(self->scroll_area, self->cursor_x, self->cursor_y, tile_index); - self->cursor_x++; - - } + terminalio_terminal_set_tile(self, false, c, true); } if (self->cursor_x >= self->scroll_area->width_in_tiles) { self->cursor_y++; @@ -294,7 +419,7 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con if (self->cursor_y != start_y) { if (((self->cursor_y + self->scroll_area->height_in_tiles) - 1) % self->scroll_area->height_in_tiles == SCRNMOD(self->vt_scroll_end)) { #if CIRCUITPY_TERMINALIO_VT100 - if (self->vt_scroll_top != 0 || self->vt_scroll_end != self->scroll_area->height_in_tiles) { + if (self->vt_scroll_top != 0 || self->vt_scroll_end != self->scroll_area->height_in_tiles - 1) { // Scroll range defined, manually move tiles to perform scroll self->cursor_y = SCRNMOD(self->vt_scroll_end); @@ -305,15 +430,18 @@ size_t common_hal_terminalio_terminal_write(terminalio_terminal_obj_t *self, con } } #endif - if (self->vt_scroll_top == 0 && self->vt_scroll_end == self->scroll_area->height_in_tiles) { + if (self->vt_scroll_top == 0 && self->vt_scroll_end == self->scroll_area->height_in_tiles - 1) { // Full screen scroll, just set new top_y pointer common_hal_displayio_tilegrid_set_top_left(self->scroll_area, 0, (self->cursor_y + self->scroll_area->height_in_tiles + 1) % self->scroll_area->height_in_tiles); } // clear the new row in case of scroll up + self->cursor_x = 0; + int16_t old_y = self->cursor_y; for (int16_t icol = 0; icol < self->scroll_area->width_in_tiles; icol++) { - common_hal_displayio_tilegrid_set_tile(self->scroll_area, icol, self->cursor_y, 0); + terminalio_terminal_set_tile(self, false, ' ', true); } self->cursor_x = 0; + self->cursor_y = old_y; } start_y = self->cursor_y; } diff --git a/shared-module/terminalio/Terminal.h b/shared-module/terminalio/Terminal.h index d617f571aed7d..8bf294cb27ff7 100644 --- a/shared-module/terminalio/Terminal.h +++ b/shared-module/terminalio/Terminal.h @@ -15,7 +15,7 @@ typedef struct { mp_obj_base_t base; - const fontio_builtinfont_t *font; + mp_obj_t font; // Can be fontio_builtinfont_t or lvfontio_ondiskfont_t uint16_t cursor_x; uint16_t cursor_y; displayio_tilegrid_t *scroll_area; @@ -30,3 +30,4 @@ typedef struct { } terminalio_terminal_obj_t; extern void terminalio_terminal_clear_status_bar(terminalio_terminal_obj_t *self); +uint16_t terminalio_terminal_get_glyph_index(mp_obj_t font, mp_uint_t codepoint, bool *is_full_width); diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 050a14ef82e34..14e5e51a49aa5 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -91,7 +91,7 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input nlr_buf_t nlr; nlr.ret_val = NULL; if (nlr_push(&nlr) == 0) { - mp_obj_t module_fun; + mp_obj_t module_fun = mp_const_none; // CIRCUITPY-CHANGE #if CIRCUITPY_ATEXIT if (!(exec_flags & EXEC_FLAG_SOURCE_IS_ATEXIT)) @@ -157,7 +157,7 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input mp_call_function_n_kw(callback->func, callback->n_pos, callback->n_kw, callback->args); } else #endif - { + if (module_fun != mp_const_none) { mp_call_function_0(module_fun); } mp_hal_set_interrupt_char(-1); // disable interrupt diff --git a/supervisor/shared/display.c b/supervisor/shared/display.c index 32df5be74efce..87af13d64688b 100644 --- a/supervisor/shared/display.c +++ b/supervisor/shared/display.c @@ -7,8 +7,10 @@ #include "supervisor/shared/display.h" #include +#include "supervisor/port.h" #include "py/mpstate.h" +#include "py/gc.h" #include "shared-bindings/displayio/Bitmap.h" #include "shared-bindings/displayio/Group.h" #include "shared-bindings/displayio/Palette.h" @@ -39,6 +41,77 @@ #if CIRCUITPY_OS_GETENV #include "shared-module/os/__init__.h" #endif +#if CIRCUITPY_LVFONTIO +#include "shared-bindings/lvfontio/OnDiskFont.h" +#include "supervisor/filesystem.h" +#include "extmod/vfs_fat.h" +#include "lib/oofatfs/ff.h" + +#include "supervisor/shared/serial.h" + +// Check if a custom font file exists and return its path if found +// Returns true if font file exists, false otherwise +static bool check_for_custom_font(const char **font_path_out) { + if (!filesystem_present()) { + return false; + } + + fs_user_mount_t *vfs = filesystem_circuitpy(); + if (vfs == NULL) { + return false; + } + + // Use FATFS directly to check if file exists + FILINFO file_info; + const char *default_font_path = "/fonts/terminal.lvfontbin"; + const char *font_path = default_font_path; + + #if CIRCUITPY_OS_GETENV + // Buffer for storing custom font path + static char custom_font_path[128]; + if (common_hal_os_getenv_str("CIRCUITPY_TERMINAL_FONT", custom_font_path, sizeof(custom_font_path)) == GETENV_OK) { + // Use custom font path from environment variable + font_path = custom_font_path; + } + #endif + + FRESULT result = f_stat(&vfs->fatfs, font_path, &file_info); + if (result == FR_OK) { + if (font_path_out != NULL) { + *font_path_out = font_path; + } + return true; + } + + // If custom font path doesn't exist, use default font + font_path = default_font_path; + result = f_stat(&vfs->fatfs, font_path, &file_info); + + if (result == FR_OK) { + if (font_path_out != NULL) { + *font_path_out = font_path; + } + return true; + } + + return false; +} + +// Initialize a BuiltinFont object with the specified font file and max_slots +// Returns true on success, false on failure +static bool init_lvfont(lvfontio_ondiskfont_t *font, const char *font_path, uint16_t max_slots) { + if (font == NULL) { + return false; + } + + font->base.type = &lvfontio_ondiskfont_type; + + // Pass false for use_gc_allocator during startup when garbage collector isn't fully initialized + common_hal_lvfontio_ondiskfont_construct(font, font_path, max_slots, false); + + return !common_hal_lvfontio_ondiskfont_deinited(font); +} +#endif #endif #if CIRCUITPY_REPL_LOGO @@ -52,29 +125,66 @@ static uint8_t *tilegrid_tiles = NULL; static size_t tilegrid_tiles_size = 0; #endif +#if CIRCUITPY_LVFONTIO +static lvfontio_ondiskfont_t *lvfont = NULL; +#endif + void supervisor_start_terminal(uint16_t width_px, uint16_t height_px) { if (supervisor_terminal_started()) { return; } + + #if CIRCUITPY_TERMINALIO // Default the scale to 2 because we may show blinka without the terminal for // languages that don't have font support. mp_int_t scale = 2; - #if CIRCUITPY_TERMINALIO displayio_tilegrid_t *scroll_area = &supervisor_terminal_scroll_area_text_grid; displayio_tilegrid_t *status_bar = &supervisor_terminal_status_bar_text_grid; bool reset_tiles = false; - uint16_t width_in_tiles = width_px / scroll_area->tile_width; + + uint16_t glyph_width = 0; + uint16_t glyph_height = 0; + + #if CIRCUITPY_LVFONTIO + // Check if we have a custom terminal font in the filesystem + bool use_lv_font = false; + const char *font_path = NULL; + + if (check_for_custom_font(&font_path)) { + // Initialize a temporary font just to get dimensions + lvfontio_ondiskfont_t temp_font; + if (init_lvfont(&temp_font, font_path, 1)) { + // Get the font dimensions + common_hal_lvfontio_ondiskfont_get_dimensions(&temp_font, &glyph_width, &glyph_height); + + // Clean up the temp font - we'll create a proper one later + common_hal_lvfontio_ondiskfont_deinit(&temp_font); + use_lv_font = true; + reset_tiles = true; + // TODO: We may want to detect when the files modified time hasn't changed. + } + } + #endif + #if CIRCUITPY_FONTIO + if (glyph_width == 0) { + glyph_width = supervisor_terminal_font.width; + glyph_height = supervisor_terminal_font.height; + } + #endif + + uint16_t width_in_tiles = width_px / glyph_width; + // determine scale based on width - if (width_in_tiles <= 80) { + if (width_in_tiles <= 120) { scale = 1; } #if CIRCUITPY_OS_GETENV (void)common_hal_os_getenv_int("CIRCUITPY_TERMINAL_SCALE", &scale); #endif - width_in_tiles = MAX(1, width_px / (scroll_area->tile_width * scale)); - uint16_t height_in_tiles = MAX(2, height_px / (scroll_area->tile_height * scale)); + width_in_tiles = MAX(1, width_px / (glyph_width * scale)); + uint16_t height_in_tiles = MAX(2, height_px / (glyph_height * scale)); uint16_t total_tiles = width_in_tiles * height_in_tiles; @@ -83,69 +193,121 @@ void supervisor_start_terminal(uint16_t width_px, uint16_t height_px) { (scroll_area->height_in_tiles != height_in_tiles - 1)) { reset_tiles = true; } - // Reuse the previous allocation if possible - if (tilegrid_tiles) { - if (tilegrid_tiles_size != total_tiles) { + + circuitpython_splash.scale = scale; + if (!reset_tiles) { + return; + } + + // Adjust the display dimensions to account for scale of the outer group. + width_px /= scale; + height_px /= scale; + + // Number of tiles from the left edge to inset the status bar. + size_t min_left_padding = 0; + status_bar->tile_width = glyph_width; + status_bar->tile_height = glyph_height; + #if CIRCUITPY_REPL_LOGO + // Blinka + 1 px padding minimum + min_left_padding = supervisor_blinka_sprite.pixel_width + 1; + // Align the status bar to the bottom of the logo. + status_bar->y = supervisor_blinka_sprite.pixel_height - status_bar->tile_height; + #else + status_bar->y = 0; + #endif + status_bar->width_in_tiles = (width_px - min_left_padding) / status_bar->tile_width; + status_bar->height_in_tiles = 1; + status_bar->pixel_width = status_bar->width_in_tiles * status_bar->tile_width; + status_bar->pixel_height = status_bar->tile_height; + // Right align the status bar. + status_bar->x = width_px - status_bar->pixel_width; + status_bar->top_left_y = 0; + status_bar->full_change = true; + + scroll_area->tile_width = glyph_width; + scroll_area->tile_height = glyph_height; + scroll_area->width_in_tiles = width_in_tiles; + // Leave space for the status bar, no matter if we have logo or not. + scroll_area->height_in_tiles = height_in_tiles - 1; + scroll_area->pixel_width = scroll_area->width_in_tiles * scroll_area->tile_width; + scroll_area->pixel_height = scroll_area->height_in_tiles * scroll_area->tile_height; + // Right align the scroll area to give margin to the start of each line. + scroll_area->x = width_px - scroll_area->pixel_width; + scroll_area->top_left_y = 0; + // Align the scroll area to the bottom so that the newest line isn't cutoff. The top line + // may be clipped by the status bar and that's ok. + scroll_area->y = height_px - scroll_area->pixel_height; + scroll_area->full_change = true; + + mp_obj_t new_bitmap = mp_const_none; + mp_obj_t new_font = mp_const_none; + + #if CIRCUITPY_LVFONTIO + if (lvfont != NULL) { + common_hal_lvfontio_ondiskfont_deinit(lvfont); + // This will also free internal buffers that may change size. + port_free(lvfont); + lvfont = NULL; + } + + if (use_lv_font) { + // We found a custom terminal font file, use it instead of the built-in font + + lvfont = port_malloc(sizeof(lvfontio_ondiskfont_t), false); + if (lvfont != NULL) { + // Use the number of tiles in the terminal and status bar for the number of slots + // This ensures we have enough slots to display all characters that could appear on screen + uint16_t num_slots = width_in_tiles * height_in_tiles; + + // Initialize the font with our helper function + if (init_lvfont(lvfont, font_path, num_slots)) { + // Get the bitmap from the font + new_bitmap = common_hal_lvfontio_ondiskfont_get_bitmap(lvfont); + new_font = MP_OBJ_FROM_PTR(lvfont); + } else { + // If font initialization failed, free the memory and fall back to built-in font + port_free(lvfont); + lvfont = NULL; + use_lv_font = false; + } + } + } + #endif + #if CIRCUITPY_FONTIO + if (new_font == mp_const_none) { + new_bitmap = MP_OBJ_FROM_PTR(supervisor_terminal_font.bitmap); + new_font = MP_OBJ_FROM_PTR(&supervisor_terminal_font); + } + #endif + + if (new_font != mp_const_none) { + size_t total_values = common_hal_displayio_bitmap_get_width(new_bitmap) / glyph_width; + if (tilegrid_tiles) { port_free(tilegrid_tiles); tilegrid_tiles = NULL; - tilegrid_tiles_size = 0; - reset_tiles = true; } - } - if (!tilegrid_tiles) { - tilegrid_tiles = port_malloc(total_tiles, false); - reset_tiles = true; + size_t bytes_per_tile = 1; + if (total_tiles > 255) { + // Two bytes per tile. + bytes_per_tile = 2; + } + tilegrid_tiles = port_malloc(total_tiles * bytes_per_tile, false); if (!tilegrid_tiles) { return; } - } - - if (reset_tiles) { - // Adjust the display dimensions to account for scale of the outer group. - width_px /= scale; - height_px /= scale; - - // Number of tiles from the left edge to inset the status bar. - size_t min_left_padding = 0; - #if CIRCUITPY_REPL_LOGO - // Blinka + 1 px padding minimum - min_left_padding = supervisor_blinka_sprite.pixel_width + 1; - // Align the status bar to the bottom of the logo. - status_bar->y = supervisor_blinka_sprite.pixel_height - status_bar->tile_height; - #else - status_bar->y = 0; - #endif - status_bar->width_in_tiles = (width_px - min_left_padding) / status_bar->tile_width; - status_bar->height_in_tiles = 1; - status_bar->pixel_width = status_bar->width_in_tiles * status_bar->tile_width; - status_bar->pixel_height = status_bar->tile_height; - // Right align the status bar. - status_bar->x = width_px - status_bar->pixel_width; - status_bar->top_left_y = 0; status_bar->tiles = tilegrid_tiles; - status_bar->full_change = true; - - scroll_area->width_in_tiles = width_in_tiles; - // Leave space for the status bar, no matter if we have logo or not. - scroll_area->height_in_tiles = height_in_tiles - 1; - scroll_area->pixel_width = scroll_area->width_in_tiles * scroll_area->tile_width; - scroll_area->pixel_height = scroll_area->height_in_tiles * scroll_area->tile_height; - // Right align the scroll area to give margin to the start of each line. - scroll_area->x = width_px - scroll_area->pixel_width; - scroll_area->top_left_y = 0; - // Align the scroll area to the bottom so that the newest line isn't cutoff. The top line - // may be clipped by the status bar and that's ok. - scroll_area->y = height_px - scroll_area->pixel_height; - scroll_area->tiles = tilegrid_tiles + width_in_tiles; - scroll_area->full_change = true; - - common_hal_terminalio_terminal_construct(&supervisor_terminal, scroll_area, &supervisor_terminal_font, status_bar); - - // Do not update status bar until after boot.py has run, in case it is disabled. + status_bar->tiles_in_bitmap = total_values; + status_bar->bitmap_width_in_tiles = total_values; + scroll_area->tiles = tilegrid_tiles + width_in_tiles * bytes_per_tile; + scroll_area->tiles_in_bitmap = total_values; + scroll_area->bitmap_width_in_tiles = total_values; + + common_hal_displayio_tilegrid_set_bitmap(scroll_area, new_bitmap); + common_hal_displayio_tilegrid_set_bitmap(status_bar, new_bitmap); + common_hal_terminalio_terminal_construct(&supervisor_terminal, scroll_area, + new_font, status_bar); } #endif - - circuitpython_splash.scale = scale; } void supervisor_stop_terminal(void) { diff --git a/tools/gen_display_resources.py b/tools/gen_display_resources.py index 2219b4e86da50..7b6de6d95d445 100644 --- a/tools/gen_display_resources.py +++ b/tools/gen_display_resources.py @@ -366,7 +366,7 @@ def _load_row(self, y, row): """\ terminalio_terminal_obj_t supervisor_terminal = { .base = { .type = &terminalio_terminal_type }, - .font = &supervisor_terminal_font, + .font = MP_OBJ_FROM_PTR(&supervisor_terminal_font), .cursor_x = 0, .cursor_y = 0, .scroll_area = NULL,