diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf new file mode 100644 index 0000000000..045f73ba40 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80135e7ab71496c3b744047ef9100f5d24edaa275d248a3e0488bc769e453baa +size 240500 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf new file mode 100644 index 0000000000..f06e31bae8 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e86fb33e127558b845aec3016d46c36118811a1e30fd9dacc374840ad3001221 +size 109356 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf new file mode 100644 index 0000000000..ccd161ef62 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71933d4233b691fb499524cbf9b3d74ccdf6465c81791687e661667aefa8e660 +size 196068 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf new file mode 100644 index 0000000000..fec43d0c6f --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:292f8ad096fd0a47e4016bd3607cb4edcd3b1f97634f67a115c0b0973ba5002f +size 245884 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf b/openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf new file mode 100644 index 0000000000..c0a05b3965 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8656eb9e4e182333e58c59cd07ae0dd82c913d31c29b0d2726887ffbbef137a2 +size 26536 diff --git a/openpilot/system/ui/lib/application.py b/openpilot/system/ui/lib/application.py index e294b02eaf..5e980937d7 100644 --- a/openpilot/system/ui/lib/application.py +++ b/openpilot/system/ui/lib/application.py @@ -19,7 +19,7 @@ from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import HARDWARE, PC -from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, UNIFONT_LANGUAGES, multilang +from openpilot.system.ui.lib.multilang import FONT_FALLBACK_LANGUAGES, TRANSLATIONS_DIR, multilang from openpilot.common.realtime import Ratekeeper _DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) @@ -93,6 +93,13 @@ FONT_SCALE = 1.242 if BIG_UI else 1.16 ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") EXTRA_FONT_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" +NOTO_FONTS = { + "ja": "NotoSansCJKjp-Regular.otf", + "ko": "NotoSansCJKkr-Regular.otf", + "th": "NotoSansThai-Regular.ttf", + "zh-CHS": "NotoSansCJKsc-Regular.otf", + "zh-CHT": "NotoSansCJKtc-Regular.otf", +} class FontWeight(StrEnum): @@ -109,9 +116,9 @@ class FontWeight(StrEnum): def font_fallback(font: rl.Font) -> rl.Font: - """Fall back to unifont for languages that require it.""" - if multilang.requires_unifont(): - return gui_app.font(FontWeight.UNIFONT) + """Use a Noto fallback for languages not covered by Inter.""" + if multilang.requires_font_fallback(): + return gui_app.fallback_font() return font @@ -199,6 +206,7 @@ class GuiApplication: self._set_log_callback() self._fonts: dict[FontWeight, rl.Font] = {} + self._fallback_fonts: dict[str, rl.Font] = {} self._width = width if width is not None else GuiApplication._default_width() self._height = height if height is not None else GuiApplication._default_height() @@ -554,6 +562,9 @@ class GuiApplication: for font in self._fonts.values(): rl.unload_font(font) self._fonts = {} + for font in self._fallback_fonts.values(): + rl.unload_font(font) + self._fallback_fonts = {} if self._render_texture is not None: rl.unload_render_texture(self._render_texture) @@ -676,6 +687,21 @@ class GuiApplication: def font(self, font_weight: FontWeight = FontWeight.NORMAL) -> rl.Font: return self._fonts[font_weight] + def fallback_font(self) -> rl.Font: + language = multilang.language + if language not in self._fallback_fonts: + chars = set(map(chr, range(32, 127))) | set(EXTRA_FONT_CHARS) + chars.update(TRANSLATIONS_DIR.joinpath(f"app_{language}.po").read_text(encoding="utf-8")) + codepoints = sorted(map(ord, chars)) + codepoint_buffer = rl.ffi.new("int[]", codepoints) + with as_file(FONT_DIR) as fspath: + font = rl.load_font_ex((fspath / NOTO_FONTS[language]).as_posix(), 48, + rl.ffi.cast("int *", codepoint_buffer), len(codepoints)) + rl.gen_texture_mipmaps(font.texture) + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) + self._fallback_fonts[language] = font + return self._fallback_fonts[language] + @property def width(self): return self._width @@ -689,8 +715,8 @@ class GuiApplication: unifont_chars = set(base_chars) for language, code in multilang.languages.items(): unifont_chars.update(language) - chars = set(TRANSLATIONS_DIR.joinpath(f"app_{code}.po").read_text(encoding="utf-8")) - (unifont_chars if code in UNIFONT_LANGUAGES else base_chars).update(chars) + if code not in FONT_FALLBACK_LANGUAGES: + base_chars.update(TRANSLATIONS_DIR.joinpath(f"app_{code}.po").read_text(encoding="utf-8")) for font_weight_file in FontWeight: with as_file(FONT_DIR) as fspath: @@ -703,6 +729,8 @@ class GuiApplication: rl.gen_texture_mipmaps(font.texture) rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) self._fonts[font_weight_file] = font + if multilang.requires_font_fallback(): + self.fallback_font() rl.gui_set_font(self._fonts[FontWeight.NORMAL]) def _set_styles(self): diff --git a/openpilot/system/ui/lib/multilang.py b/openpilot/system/ui/lib/multilang.py index 3d786a7248..6bdf18f1cd 100644 --- a/openpilot/system/ui/lib/multilang.py +++ b/openpilot/system/ui/lib/multilang.py @@ -19,7 +19,7 @@ UI_DIR = files("openpilot.selfdrive.ui") TRANSLATIONS_DIR = UI_DIR.joinpath("translations") LANGUAGES_FILE = TRANSLATIONS_DIR.joinpath("languages.json") -UNIFONT_LANGUAGES = [ +FONT_FALLBACK_LANGUAGES = [ "th", "zh-CHT", "zh-CHS", @@ -164,9 +164,8 @@ class Multilang: def language(self) -> str: return self._language - def requires_unifont(self) -> bool: - """Certain languages require unifont to render their glyphs.""" - return self._language in UNIFONT_LANGUAGES + def requires_font_fallback(self) -> bool: + return self._language in FONT_FALLBACK_LANGUAGES def setup(self): try: