mirror of https://github.com/koreader/koreader
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
6.9 KiB
Lua
190 lines
6.9 KiB
Lua
5 years ago
|
--[[--
|
||
|
Bidirectional text and UI mirroring setup and helpers.
|
||
|
|
||
|
There are 2 concepts we attempt to handle:
|
||
|
- Text direction: Left-To-Right (LTR) or Right-To-Left (RTL)
|
||
|
- UI elements mirroring: not-mirrored, or mirrored
|
||
|
|
||
|
These 2 concepts are somehow orthogonal to each other in
|
||
|
their implementation, even if in the real world there are
|
||
|
only 2 valid combinations:
|
||
|
- LTR and not-mirrored: for western languages, CJK, Indic...
|
||
|
- RTL and mirrored: for Arabic, Hebrew, Farsi and a few others.
|
||
|
|
||
|
Text direction is handled by the libkoreader-xtext.so C module,
|
||
|
and the TextWidget and TextBoxWidget widgets that handle text
|
||
|
aligment. We just need here to set the default global paragraph
|
||
|
direction (that widgets can override if needed).
|
||
|
|
||
|
UI mirroring is to be handled by our widget themselves, with the
|
||
|
help of a few functions defined here.
|
||
|
|
||
|
Fortunately, low level widgets like LeftContainer, RightContainer,
|
||
|
FrameContainer, HorizontalGroup, OverlapGroup... will do most of
|
||
|
the work.
|
||
|
But some care must be taken in other widgets and apps when:
|
||
|
- some arrow symbols are used (for next, previous, first, last...):
|
||
|
they might need to be swapped, or some alternative symbols or
|
||
|
images can be used.
|
||
|
- some geometry arithmetic is done (e.g. detecting if a tap is on the
|
||
|
right part of screen, to go forward), which need to be adapted/reversed.
|
||
|
- handling left or right swipe, whose action might need to be reversed
|
||
|
- some TextBoxWidget/InputText might need to be forced to be LTR (when
|
||
|
showing HTML or CSS code, or entering URLs, path...)
|
||
|
|
||
|
Some overview at:
|
||
|
https://material.io/design/usability/bidirectionality.html
|
||
|
]]
|
||
|
|
||
|
local Language = require("ui/language")
|
||
|
local _ = require("gettext")
|
||
|
|
||
|
local Bidi = {
|
||
|
_mirrored_ui_layout = false,
|
||
|
_rtl_ui_text = false,
|
||
|
}
|
||
|
|
||
|
-- Setup UI mirroring and RTL text for UI language
|
||
|
function Bidi.setup(lang)
|
||
|
local is_rtl = Language:isLanguageRTL(lang)
|
||
|
-- Mirror UI if language is RTL
|
||
|
Bidi._mirrored_ui_layout = is_rtl
|
||
|
-- Unless requested not to (or requested mirroring with LTR language)
|
||
|
if G_reader_settings:isTrue("dev_reverse_ui_layout_mirroring") then
|
||
|
Bidi._mirrored_ui_layout = not Bidi._mirrored_ui_layout
|
||
|
end
|
||
|
-- Xtext default language and direction
|
||
|
if G_reader_settings:nilOrTrue("use_xtext") then
|
||
|
local xtext = require("libs/libkoreader-xtext")
|
||
|
|
||
|
-- Text direction should normally not follow ui mirroring
|
||
|
-- lang override (so that Arabic is still right aligned
|
||
|
-- when one wants the UI layout LTR). But allow it to
|
||
|
-- be independantly reversed (for testing UI mirroring
|
||
|
-- with english text right aligned).
|
||
|
if G_reader_settings:isTrue("dev_reverse_ui_text_direction") then
|
||
|
is_rtl = not is_rtl
|
||
|
end
|
||
|
Bidi._rtl_ui_text = is_rtl
|
||
|
xtext.setDefaultParaDirection(is_rtl)
|
||
|
|
||
|
-- Text language: this helps picking localized glyphs from the
|
||
|
-- font (eg. ideographs shaped differently for Japanese vs
|
||
|
-- Simplified Chinese vs Traditional Chinese).
|
||
|
-- Allow overriding xtext language rules from main UI language
|
||
|
-- (eg. English UI, with French line breaking rules)
|
||
|
local alt_lang = G_reader_settings:readSetting("xtext_alt_lang") or lang
|
||
|
if alt_lang then
|
||
|
xtext.setDefaultLang(alt_lang)
|
||
|
end
|
||
|
end
|
||
|
-- Optimise Bidi.default and Bidi.wrap by aliasing them to the right wrappers
|
||
|
if Bidi._rtl_ui_text then
|
||
|
Bidi.default = Bidi.rtl
|
||
|
Bidi.wrap = Bidi.rtl
|
||
|
else
|
||
|
Bidi.default = Bidi.ltr
|
||
|
Bidi.wrap = Bidi.noop
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Use this function in widgets to check if UI elements mirroring
|
||
|
-- is to be done
|
||
|
function Bidi.mirroredUILayout()
|
||
|
return Bidi._mirrored_ui_layout
|
||
|
end
|
||
|
|
||
|
-- This function might only be useful in some rare cases (RTL text
|
||
|
-- is handled directly by TextWidget and TextBoxWidget)
|
||
|
function Bidi.rtlUIText()
|
||
|
return Bidi._rtl_ui_text
|
||
|
end
|
||
|
|
||
|
-- Small helper to mirror gesture directions
|
||
|
local mirrored_directions = {
|
||
|
east = "west",
|
||
|
west = "east",
|
||
|
northeast = "northwest",
|
||
|
northwest = "northeast",
|
||
|
southeast = "southwest",
|
||
|
southwest = "southeast",
|
||
|
}
|
||
|
|
||
|
function Bidi.flipDirectionIfMirroredUILayout(direction)
|
||
|
if Bidi._mirrored_ui_layout then
|
||
|
return mirrored_directions[direction] or direction
|
||
|
end
|
||
|
return direction
|
||
|
end
|
||
|
|
||
|
function Bidi.flipIfMirroredUILayout(bool)
|
||
|
if Bidi._mirrored_ui_layout then
|
||
|
return not bool
|
||
|
end
|
||
|
return bool
|
||
|
end
|
||
|
|
||
|
-- Wrap provided text with bidirectionality control characters, see:
|
||
|
-- http://unicode.org/reports/tr9/#Markup_And_Formatting
|
||
|
-- https://www.w3.org/International/questions/qa-bidi-unicode-controls.en
|
||
|
-- https://www.w3.org/International/articles/inline-bidi-markup/
|
||
|
-- This works only when use_xtext=true: these characters are used
|
||
|
-- by FriBidi for correct char visual ordering, and later stripped
|
||
|
-- by Harfbuzz.
|
||
|
-- When use_xtext=false, these characters are considered as normal
|
||
|
-- characters, and would be printed. Fortunately, most fonts know them
|
||
|
-- and provide an invisible glyph of zero-width - except FreeSans and
|
||
|
-- FreeSerif which provide a real glyph (a square with "LRI" inside)
|
||
|
-- which would be an issue and would need stripping. But as these
|
||
|
-- Free fonts are only used as fallback fonts, and the invisible glyphs
|
||
|
-- will have been found in the previous fonts, we don't need to.
|
||
|
local LRI = "\xE2\x81\xA6" -- U+2066 LRI / LEFT-TO-RIGHT ISOLATE
|
||
|
local RLI = "\xE2\x81\xA7" -- U+2067 RLI / RIGHT-TO-LEFT ISOLATE
|
||
|
local FSI = "\xE2\x81\xA8" -- U+2068 FSI / FIRST STRONG ISOLATE
|
||
|
local PDI = "\xE2\x81\xA9" -- U+2069 PDI / POP DIRECTIONAL ISOLATE
|
||
|
|
||
|
function Bidi.ltr(text)
|
||
|
return string.format("%s%s%s", LRI, text, PDI)
|
||
|
end
|
||
|
|
||
|
function Bidi.rtl(text) -- should hardly be needed
|
||
|
return string.format("%s%s%s", RLI, text, PDI)
|
||
|
end
|
||
|
|
||
|
function Bidi.auto(text) -- from first strong character
|
||
|
return string.format("%s%s%s", FSI, text, PDI)
|
||
|
end
|
||
|
|
||
|
function Bidi.default(text) -- default direction
|
||
|
return Bidi._rtl_ui_text and Bidi.rtl(text) or Bidi.ltr(text)
|
||
|
end
|
||
|
|
||
|
function Bidi.noop(text) -- no wrap
|
||
|
return text
|
||
|
end
|
||
|
|
||
|
-- Helper for concatenated string bits of numbers an symbols (like
|
||
|
-- our reader footer) to keep them ordered in RTL UI (to not have
|
||
|
-- a letter B for battery make the whole string considered LTR).
|
||
|
-- Note: it will be replaced and aliased to Bidi.noop or Bidi.rtl
|
||
|
-- by Bibi.setup() as an optimisation
|
||
|
function Bidi.wrap(text)
|
||
|
return Bidi._rtl_ui_text and Bidi.rtl(text) or text
|
||
|
end
|
||
|
|
||
|
-- See at having GetText_mt.__call() wrap untranslated strings in Bidi.ltr()
|
||
|
-- so they are fully displayed LTR.
|
||
|
|
||
|
-- Use these specific wrappers when the wrapped content type is known
|
||
|
-- (so we can easily switch to use rtl() if RTL readers prefer filenames
|
||
|
-- shown as real RTL).
|
||
|
-- Note: when the filename or path are standalone in a TextWidget, it's
|
||
|
-- better to use "para_direction_rtl = false" without any wrapping.
|
||
|
Bidi.filename = Bidi.ltr
|
||
|
Bidi.directory = Bidi.ltr
|
||
|
Bidi.path = Bidi.ltr
|
||
|
Bidi.url = Bidi.ltr
|
||
|
|
||
|
return Bidi
|