From 21932c24a2cd2843d46487f45fcf3796425a2498 Mon Sep 17 00:00:00 2001 From: poire-z Date: Tue, 25 Jan 2022 01:08:33 +0100 Subject: [PATCH] UI font fallbacks: allow using more "Noto Sans xyz" (#8722) Add a submenu allowing users to add Noto Sans fonts to be used as UI fallback fonts. Lots of them are alrady there on Android, but can be downloaded (manually) and side-loaded by the user on other platforms. --- .../elements/common_settings_menu_table.lua | 2 + .../ui/elements/filemanager_menu_order.lua | 2 + frontend/ui/elements/font_ui_fallbacks.lua | 160 ++++++++++++++++++ frontend/ui/elements/reader_menu_order.lua | 2 + frontend/ui/font.lua | 16 ++ 5 files changed, 182 insertions(+) create mode 100644 frontend/ui/elements/font_ui_fallbacks.lua diff --git a/frontend/ui/elements/common_settings_menu_table.lua b/frontend/ui/elements/common_settings_menu_table.lua index 2f8ce8ee4..0259f3da5 100644 --- a/frontend/ui/elements/common_settings_menu_table.lua +++ b/frontend/ui/elements/common_settings_menu_table.lua @@ -580,6 +580,8 @@ common_settings.document_end_action = { common_settings.language = Language:getLangMenuTable() +common_settings.font_ui_fallbacks = require("ui/elements/font_ui_fallbacks") + common_settings.screenshot = { text = _("Screenshot folder"), callback = function() diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 0a75db7ea..0b18444cb 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -43,6 +43,8 @@ local order = { }, device = { "keyboard_layout", + "font_ui_fallbacks", + "----------------------------", "time", "device_status_alarm", "charging_led", -- if Device:canToggleChargingLED() diff --git a/frontend/ui/elements/font_ui_fallbacks.lua b/frontend/ui/elements/font_ui_fallbacks.lua new file mode 100644 index 000000000..7c1789425 --- /dev/null +++ b/frontend/ui/elements/font_ui_fallbacks.lua @@ -0,0 +1,160 @@ +local FFIUtil = require("ffi/util") +local Font = require("ui/font") +local FontList = require("fontlist") +local InfoMessage = require("ui/widget/infomessage") +local UIManager = require("ui/uimanager") +local util = require("util") +local _ = require("gettext") +local T = FFIUtil.template + +-- Some "Noto Sans *" fonts are already in ui/font.lua Font.fallbacks, +-- because they are required to display supported UI languages. +-- Don't allow them to be disabled. +-- Duplicated from (and should be kept in sync with) ui/font.lua, but here +-- by face name and there by font file/path name. +local hardcoded_fallbacks = { + "Noto Sans CJK SC", + "Noto Sans Arabic UI", + "Noto Sans Devanagari UI", +} +-- Add any user font after Noto Sans CJK SC in the menu +local additional_fallback_insert_indice = 2 -- (indice in the above list) + +local alt_name = { + -- Make it clear this "CJK SC" font actually supports all other CJK variant + ["Noto Sans CJK SC"] = "Noto Sans CJK SC (TC, JA, KO)" +} + +local fallback_candidates = nil +local fallback_candidates_path_to_name = nil + +local genFallbackCandidates = function() + if fallback_candidates then + return + end + fallback_candidates = {} + fallback_candidates_path_to_name = {} + for _, font_path in ipairs(FontList:getFontList()) do + local fontinfo = FontList.fontinfo[font_path] -- (NotoColorEmoji.tff happens to get no fontinfo) + if fontinfo and #fontinfo == 1 then -- Ignore font files with multiple faces + fontinfo = fontinfo[1] + if util.stringStartsWith(fontinfo.name, "Noto Sans ") and + not fontinfo.bold and not fontinfo.italic and + not fontinfo.serif and not fontinfo.mono then + fallback_candidates[fontinfo.name] = fontinfo + fallback_candidates_path_to_name[font_path] = fontinfo.name + end + end + end +end + +local more_info_text = [[ +If some book titles, dictionary entries and such are not displayed well but shown as ￾￾ or ��, it may be necessary to download the required fonts for those languages. They can then be enabled as additional UI fallback fonts. +Fonts for many languages can be downloaded at: + +https://fonts.google.com/noto + +Only fonts named "Noto Sans xyz" or "Noto Sans xyz UI" (regular, not bold nor italic, not Serif) will be available in this menu.]] + +local getSubMenuItems = function() + genFallbackCandidates() + -- Order the menu items in the order the fallback fonts are used + local seen_names = {} + local ordered_names = {} + local checked_names = {} + local enabled_names = {} + for _, name in ipairs(hardcoded_fallbacks) do + table.insert(ordered_names, name) + seen_names[name] = true + checked_names[name] = true + enabled_names[name] = false + end + if G_reader_settings:has("font_ui_fallbacks") then + local additional_fallbacks = G_reader_settings:readSetting("font_ui_fallbacks") + for i=#additional_fallbacks, 1, -1 do + local path = additional_fallbacks[i] + local name = fallback_candidates_path_to_name[path] + if not name or seen_names[name] then + -- No longer found, or made hardcoded: remove it + table.remove(additional_fallbacks, i) + else + table.insert(ordered_names, additional_fallback_insert_indice, name) + seen_names[name] = true + checked_names[name] = true + enabled_names[name] = true + end + end + if #additional_fallbacks == 0 then -- all removed + G_reader_settings:delSetting("font_ui_fallbacks") + end + end + local add_separator_idx = #ordered_names + for name, fontinfo in FFIUtil.orderedPairs(fallback_candidates) do + if not seen_names[name] then + table.insert(ordered_names, name) + checked_names[name] = false + enabled_names[name] = true + end + end + + local menu_items = { + { + text = _("About additional UI fallback fonts"), + callback = function() + UIManager:show(InfoMessage:new{ + text = more_info_text, + }) + end, + keep_menu_open = true, + separator = true, + } + } + for idx, name in ipairs(ordered_names) do + local fontinfo = fallback_candidates[name] + local item = { + text = alt_name[name] or name, + separator = idx == add_separator_idx, + checked_func = function() + return checked_names[name] + end, + enabled_func = function() + return enabled_names[name] + end, + callback = function() + local additional_fallbacks = G_reader_settings:readSetting("font_ui_fallbacks", {}) + if checked_names[name] then -- enabled: remove it + for i=#additional_fallbacks, 1, -1 do + if additional_fallbacks[i] == fontinfo.path then + table.remove(additional_fallbacks, i) + if #additional_fallbacks == 0 then + G_reader_settings:delSetting("font_ui_fallbacks") + end + break + end + end + checked_names[name] = false + else -- disabled: add it + if #additional_fallbacks < Font.additional_fallback_max_nb then + table.insert(additional_fallbacks, 1, fontinfo.path) + checked_names[name] = true + else + UIManager:show(InfoMessage:new{ + text = T(_("The number of allowed additional fallback fonts is limited to %1.\nUncheck some of them if you want to add this one."), Font.additional_fallback_max_nb), + }) + return + end + end + UIManager:show(InfoMessage:new{ + text = _("This will take effect on next restart."), + }) + end, + } + table.insert(menu_items, item) + end + return menu_items +end + +return { + text = _("Additional UI fallback fonts"), + sub_item_table_func = getSubMenuItems, +} diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index fe3495b52..3a5be9f15 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -84,6 +84,8 @@ local order = { }, device = { "keyboard_layout", + "font_ui_fallbacks", + "----------------------------", "time", "device_status_alarm", "charging_led", -- if Device:canToggleChargingLED() diff --git a/frontend/ui/font.lua b/frontend/ui/font.lua index e950b836b..48f55db88 100644 --- a/frontend/ui/font.lua +++ b/frontend/ui/font.lua @@ -113,11 +113,27 @@ local Font = { [6] = "freefont/FreeSans.ttf", [7] = "freefont/FreeSerif.ttf", }, + -- Additional fallback fonts are managed by frontend/ui/elements/font_ui_fallbacks.lua + -- Add any after NotoSansCJKsc (because CJKsc has better symbols, and has 'locl' OTF + -- features to support all of SC, TC, JA and KO that other CJK fonts may not have.) + additional_fallback_insert_indice = 3, + -- Xtext supports up to 15 fallback fonts, but keep some slots free and available for + -- future additions to our hardcoded fallbacks list above, and to not slow down + -- rendering with too many fallback fonts. + additional_fallback_max_nb = 4, -- face table faces = {}, } +if G_reader_settings and G_reader_settings:has("font_ui_fallbacks") then + local additional_fallbacks = G_reader_settings:readSetting("font_ui_fallbacks") + for i=#additional_fallbacks, 1, -1 do + table.insert(Font.fallbacks, Font.additional_fallback_insert_indice, additional_fallbacks[i]) + end + logger.dbg("updated Font.fallbacks:", Font.fallbacks) +end + -- Helper functions with explicite names around -- bold/regular_font_variant tables function Font:hasBoldVariant(name)