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