TextBoxWidget: slightly better cursor positionning in RTL text

reviewable/pr5670/r1
poire-z 5 years ago
parent 588c35967f
commit e9032a2b2a

@ -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

Loading…
Cancel
Save