mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
TextBoxWidget: slightly better cursor positionning in RTL text
This commit is contained in:
@@ -447,15 +447,46 @@ end
|
||||
-- XText: shape a line into positionned glyphs
|
||||
function TextBoxWidget:_shapeLine(line)
|
||||
-- line is an item from self.vertical_string_list
|
||||
if not line.end_offset then
|
||||
return -- empty line (hard newline at end of file)
|
||||
end
|
||||
if line.end_offset < line.offset then
|
||||
return -- empty line (hard newline while not at end of file)
|
||||
end
|
||||
if line.xglyphs then
|
||||
if line._shaped then
|
||||
return -- already done
|
||||
end
|
||||
line._shaped = true
|
||||
if not line.end_offset or line.end_offset < line.offset then
|
||||
-- Empty line (first check above is for hard newline at end of file,
|
||||
-- second check is for hard newline while not at end of file).
|
||||
-- We need to set a direction on this line, so the cursor can be
|
||||
-- positionned accordingly, on the left or on the right of the line
|
||||
-- (for convenience, we have an empty line inherit the direction
|
||||
-- of the previous line if non-empty)
|
||||
local offset = line.offset
|
||||
if not line.end_offset then -- last line with offset=#text+1
|
||||
if offset > 1 then -- non empty text: get it from last char
|
||||
offset = offset - 1
|
||||
else
|
||||
offset = nil -- no text: get _xtext specified or default direction
|
||||
end
|
||||
end
|
||||
local para_dir_rtl, prev_char_para_dir_rtl = self._xtext:getParaDirection(offset)
|
||||
line.para_is_rtl = para_dir_rtl or prev_char_para_dir_rtl
|
||||
-- We also need to set x_start & x_end (similar to how we do it below)
|
||||
local alignment = self.alignment
|
||||
if not self.alignment_strict and line.para_is_rtl then
|
||||
if alignment == "left" then
|
||||
alignment = "right"
|
||||
elseif alignment == "right" then
|
||||
alignment = "left"
|
||||
end
|
||||
end
|
||||
local pen_x = 0 -- when alignment == "left"
|
||||
if alignment == "center" then
|
||||
pen_x = line.targeted_width / 2
|
||||
elseif alignment == "right" then
|
||||
pen_x = line.targeted_width
|
||||
end
|
||||
line.x_start = pen_x
|
||||
line.x_end = pen_x
|
||||
return
|
||||
end
|
||||
-- Get glyphs, shaped and possibly substituted by Harfbuzz and re-ordered by FriBiDi.
|
||||
-- We'll add to 'line' this table of glyphs, with some additional
|
||||
-- computed x and advance keys
|
||||
@@ -469,6 +500,7 @@ function TextBoxWidget:_shapeLine(line)
|
||||
-- ["can_extend"] = false,
|
||||
-- ["can_extend_fallback"] = false,
|
||||
-- ["is_rtl"] = false,
|
||||
-- ["bidi_level"] = 0,
|
||||
-- ["text_index"] = 1,
|
||||
-- ["glyph"] = 68,
|
||||
-- ["font_num"] = 0,
|
||||
@@ -483,6 +515,7 @@ function TextBoxWidget:_shapeLine(line)
|
||||
-- ["can_extend"] = false,
|
||||
-- ["can_extend_fallback"] = false,
|
||||
-- ["is_rtl"] = true,
|
||||
-- ["bidi_level"] = 1,
|
||||
-- ["text_index"] = 8,
|
||||
-- ["glyph"] = 1292,
|
||||
-- ["font_num"] = 3,
|
||||
@@ -496,6 +529,7 @@ function TextBoxWidget:_shapeLine(line)
|
||||
-- ["can_extend"] = false,
|
||||
-- ["can_extend_fallback"] = false,
|
||||
-- ["is_rtl"] = true,
|
||||
-- ["bidi_level"] = 1,
|
||||
-- ["text_index"] = 8,
|
||||
-- ["glyph"] = 1321,
|
||||
-- ["font_num"] = 3,
|
||||
@@ -549,6 +583,7 @@ function TextBoxWidget:_shapeLine(line)
|
||||
end
|
||||
end
|
||||
|
||||
line.x_start = pen_x
|
||||
local prev_cluster_start_xglyph
|
||||
for i, xglyph in ipairs(xshaping) do
|
||||
xglyph.x0 = pen_x
|
||||
@@ -568,7 +603,7 @@ function TextBoxWidget:_shapeLine(line)
|
||||
-- with advance or zero-advance...), glyphs may not always be fine to position
|
||||
-- the cursor caret. For X/Y/Charpos positionning/guessing, we'll ignore
|
||||
-- glyphs that are not cluster_start, and we build here the full cluster x0/x1/w
|
||||
-- by mergin them from all glyphs part of this cluster
|
||||
-- by merging them from all glyphs part of this cluster
|
||||
if xglyph.is_cluster_start then
|
||||
prev_cluster_start_xglyph = xglyph
|
||||
else
|
||||
@@ -580,7 +615,10 @@ function TextBoxWidget:_shapeLine(line)
|
||||
-- has a backward advance that go back the 1st glyph x0, to not mess positionning.
|
||||
end
|
||||
end
|
||||
line.x_end = pen_x
|
||||
line.xglyphs = xshaping
|
||||
-- (Copy para_is_rtl up into 'line', where empty lines without xglyphs have it)
|
||||
line.para_is_rtl = line.xglyphs.para_is_rtl
|
||||
--- @todo Should we drop these when no more displayed in the page to reclaim memory,
|
||||
-- at the expense of recomputing it when back to this page?
|
||||
end
|
||||
@@ -1116,18 +1154,36 @@ function TextBoxWidget:_getXYForCharPos(charpos)
|
||||
end
|
||||
end
|
||||
local y = (ln - self.virtual_line_num) * self.line_height_px
|
||||
|
||||
-- Find the x offset in the current line.
|
||||
local x = 0
|
||||
|
||||
if self.use_xtext then
|
||||
local line = self.vertical_string_list[ln]
|
||||
self:_shapeLine(line)
|
||||
local x = line.x_start -- used if empty line (line.x_start = line.x_end)
|
||||
if line.xglyphs then -- non-empty line
|
||||
-- If charpos is the end of the logical order line, it may not be at end of
|
||||
-- visual line (it might be at start, or even in the middle, with bidi!)
|
||||
local is_after_last_char = charpos > line.end_offset
|
||||
if is_after_last_char then
|
||||
-- Find the last char that is really part of this line
|
||||
charpos = line.end_offset
|
||||
end
|
||||
for i, xglyph in ipairs(line.xglyphs) do
|
||||
if xglyph.is_cluster_start then -- ignore non-start cluster glyphs
|
||||
if charpos >= xglyph.text_index and charpos < xglyph.text_index + xglyph.cluster_len then
|
||||
-- Correct glyph found
|
||||
if is_after_last_char then
|
||||
-- Draw on the right of this glyph if LTR, on the left if RTL
|
||||
if xglyph.is_rtl then
|
||||
x = xglyph.x0
|
||||
else
|
||||
x = xglyph.x1
|
||||
end
|
||||
break
|
||||
end
|
||||
--- @todo Be more clever with RTL, and at bidi boundaries,
|
||||
-- may be depending on line.xglyphs.para_is_rtl.
|
||||
-- may be depending on line.para_is_rtl and xglyph.bidi_level
|
||||
if xglyph.is_rtl then
|
||||
x = xglyph.x1 -- draw cursor on the right of this RTL glyph
|
||||
else
|
||||
@@ -1146,8 +1202,6 @@ function TextBoxWidget:_getXYForCharPos(charpos)
|
||||
break
|
||||
end
|
||||
x = xglyph.x1
|
||||
--- @todo When line.xglyphs.para_is_rtl and no x found, it should
|
||||
-- be the first line glyph's x0
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1157,6 +1211,7 @@ function TextBoxWidget:_getXYForCharPos(charpos)
|
||||
|
||||
-- Only when not self.use_xtext:
|
||||
|
||||
local x = 0
|
||||
local offset = self.vertical_string_list[ln].offset
|
||||
local nbchars = #self.charlist
|
||||
while offset < charpos do
|
||||
@@ -1185,13 +1240,6 @@ function TextBoxWidget:getCharPosAtXY(x, y)
|
||||
elseif ln > #self.vertical_string_list then
|
||||
return #self.charlist + 1 -- return end of last line
|
||||
end
|
||||
if x > self.vertical_string_list[ln].width then -- no need to loop thru chars
|
||||
local pos = self.vertical_string_list[ln].end_offset
|
||||
if not pos then -- empty last line
|
||||
return self.vertical_string_list[ln].offset
|
||||
end
|
||||
return pos + 1 -- after last char
|
||||
end
|
||||
local idx = self.vertical_string_list[ln].offset
|
||||
local end_offset = self.vertical_string_list[ln].end_offset
|
||||
if not end_offset then -- empty line
|
||||
@@ -1201,7 +1249,21 @@ function TextBoxWidget:getCharPosAtXY(x, y)
|
||||
if self.use_xtext then
|
||||
local line = self.vertical_string_list[ln]
|
||||
self:_shapeLine(line)
|
||||
--- @todo Probably some specific/inverted work if line.xglyphs.para_is_rtl
|
||||
-- If before start of line or after end of line, no need to loop thru chars
|
||||
-- (we return line.end_offset+1 to be after last char)
|
||||
if x <= line.x_start then
|
||||
if line.para_is_rtl then
|
||||
return line.end_offset and line.end_offset + 1 or line.offset
|
||||
else
|
||||
return line.offset
|
||||
end
|
||||
elseif x > line.x_end then
|
||||
if line.para_is_rtl then
|
||||
return line.offset
|
||||
else
|
||||
return line.end_offset and line.end_offset + 1 or line.offset
|
||||
end
|
||||
end
|
||||
if line.xglyphs then -- non-empty line
|
||||
for i, xglyph in ipairs(line.xglyphs) do
|
||||
if xglyph.is_cluster_start then -- ignore non-start cluster glyphs
|
||||
@@ -1232,6 +1294,13 @@ function TextBoxWidget:getCharPosAtXY(x, y)
|
||||
|
||||
-- Only when not self.use_xtext:
|
||||
|
||||
if x > self.vertical_string_list[ln].width then -- no need to loop thru chars
|
||||
local pos = self.vertical_string_list[ln].end_offset
|
||||
if not pos then -- empty last line
|
||||
return self.vertical_string_list[ln].offset
|
||||
end
|
||||
return pos + 1 -- after last char
|
||||
end
|
||||
local w = 0
|
||||
local w_prev
|
||||
while idx <= end_offset do
|
||||
|
||||
Reference in New Issue
Block a user