diff --git a/frontend/ui/rendertext.lua b/frontend/ui/rendertext.lua index e61e3bd52..007e3b1d6 100644 --- a/frontend/ui/rendertext.lua +++ b/frontend/ui/rendertext.lua @@ -192,8 +192,9 @@ end -- @bool[opt=false] bold whether the text should be measured as bold -- @tparam[opt=BlitBuffer.COLOR_BLACK] BlitBuffer.COLOR fgcolor foreground color -- @int[opt=nil] width maximum rendering width +-- @tparam[opt] table char_pads array of integers, nb of pixels to add, one for each utf8 char in text -- @return int width of rendered bitmap -function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bold, fgcolor, width) +function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bold, fgcolor, width, char_pads) if not text then logger.warn("renderUtf8Text called without text"); return 0 @@ -211,6 +212,7 @@ function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bo if width and width < text_width then text_width = width end + local char_idx = 0 for _, charcode, uchar in utf8Chars(text) do if pen_x < text_width then local glyph = self:getGlyph(face, charcode, bold) @@ -227,6 +229,11 @@ function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bo pen_x = pen_x + glyph.ax prevcharcode = charcode end -- if pen_x < text_width + if char_pads then + char_idx = char_idx + 1 + pen_x = pen_x + char_pads[char_idx] -- or 0 + -- will fail if we didnt count the same number of chars, we'll see + end end return pen_x diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index b16102c76..1f979c260 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -42,6 +42,8 @@ local DictQuickLookup = InputContainer:new{ height = nil, -- box of highlighted word, quick lookup window tries to not hide the word word_box = nil, + -- allow for disabling justification + dict_justify = G_reader_settings:nilOrTrue("dict_justify"), title_padding = Screen:scaleBySize(5), title_margin = Screen:scaleBySize(2), @@ -231,6 +233,7 @@ function DictQuickLookup:update() -- get a bit more height for definition as wiki has one less button raw height = self.is_fullpage and self.height*0.75 or self.height*0.7, dialog = self, + justified = self.dict_justify, }, } -- Different sets of buttons if fullpage or not diff --git a/frontend/ui/widget/scrolltextwidget.lua b/frontend/ui/widget/scrolltextwidget.lua index d1cafb597..6f423c296 100644 --- a/frontend/ui/widget/scrolltextwidget.lua +++ b/frontend/ui/widget/scrolltextwidget.lua @@ -19,12 +19,13 @@ local ScrollTextWidget = InputContainer:new{ charlist = nil, charpos = nil, editable = false, + justified = false, face = nil, fgcolor = Blitbuffer.COLOR_BLACK, width = 400, height = 20, scroll_bar_width = Screen:scaleBySize(6), - text_scroll_span = Screen:scaleBySize(6), + text_scroll_span = Screen:scaleBySize(12), dialog = nil, } @@ -34,6 +35,7 @@ function ScrollTextWidget:init() charlist = self.charlist, charpos = self.charpos, editable = self.editable, + justified = self.justified, face = self.face, fgcolor = self.fgcolor, width = self.width - self.scroll_bar_width - self.text_scroll_span, diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index cf77d1e0c..f3f7ef33e 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -29,6 +29,7 @@ local TextBoxWidget = Widget:new{ char_width_list = nil, -- list of widths of the chars in `charlist`. vertical_string_list = nil, editable = false, -- Editable flag for whether drawing the cursor or not. + justified = false, -- Should text be justified (spaces widened to fill width) cursor_line = nil, -- LineWidget to draw the vertical cursor. face = nil, bold = nil, @@ -88,7 +89,8 @@ function TextBoxWidget:_evalCharWidthList() w = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, v, true, self.bold).x char_width_cache[v] = w end - table.insert(self.char_width_list, {char = v, width = w}) + table.insert(self.char_width_list, {char = v, width = w, pad = 0}) + -- pad will be updated if we do text justification end end @@ -106,6 +108,7 @@ function TextBoxWidget:_splitCharWidthList() -- or a newline occurs, or no more chars to consume. cur_line_width = 0 local hard_newline = false + local char_pads = nil while idx <= size do if self.char_width_list[idx].char == "\n" then hard_newline = true @@ -137,6 +140,12 @@ function TextBoxWidget:_splitCharWidthList() -- we let that excessive char for next line cur_line_text = table.concat(self.charlist, "", offset, idx - 1) cur_line_width = cur_line_width - self.char_width_list[idx].width + elseif c == " " then + -- we backtracked and we're below max width, but the last char + -- is a space, we can ignore it + cur_line_text = table.concat(self.charlist, "", offset, adjusted_idx - 1) + cur_line_width = adjusted_width - self.char_width_list[adjusted_idx].width + idx = adjusted_idx + 1 else -- we backtracked and we're below max width, we can let the -- splitable char on this line @@ -144,12 +153,56 @@ function TextBoxWidget:_splitCharWidthList() cur_line_width = adjusted_width idx = adjusted_idx + 1 end + if self.justified then + -- this line was splitted and can be justified + -- we build a list of char_pads, pixels to add to some chars to make the + -- whole line justified + local fill_width = self.width - cur_line_width + if fill_width > 0 then + local _, nbspaces = string.gsub(cur_line_text, " ", "") + if nbspaces > 0 then + -- width added to all spaces + local space_add_w = math.floor(fill_width / nbspaces) + -- nb of spaces to which we'll add 1 more pixel + local space_add1_nb = fill_width - space_add_w * nbspaces + char_pads = {} + for cidx = offset, idx-1 do + local pad = 0 + if self.char_width_list[cidx].char == " " then + pad = space_add_w + if space_add1_nb > 0 then + pad = pad + 1 + space_add1_nb = space_add1_nb - 1 + end + -- Update pad info, help for hold position accuracy + self.char_width_list[cidx].pad = pad + end + table.insert(char_pads, pad) + end + else + -- very long word, or CJK text with no space + -- pad first chars with 1 pixel + char_pads = {} + for cidx = offset, idx-1 do + local pad = 0 + if fill_width > 0 then + pad = 1 + fill_width = fill_width - 1 + -- Update pad info, help for hold position accuracy + self.char_width_list[cidx].pad = pad + end + table.insert(char_pads, pad) + end + end + end + end end -- endif cur_line_width > self.width if cur_line_width < 0 then break end self.vertical_string_list[ln] = { text = cur_line_text, offset = offset, - width = cur_line_width + width = cur_line_width, + char_pads = char_pads, } if hard_newline then idx = idx + 1 @@ -181,7 +234,7 @@ function TextBoxWidget:_renderText(start_row_idx, end_row_idx) local pen_x = self.alignment == "center" and (self.width - line.width)/2 or 0 --@TODO Don't use kerning for monospaced fonts. (houqp) -- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree - RenderText:renderUtf8Text(self._bb, pen_x, y, self.face, line.text, true, self.bold, self.fgcolor) + RenderText:renderUtf8Text(self._bb, pen_x, y, self.face, line.text, true, self.bold, self.fgcolor, nil, line.char_pads) y = y + self.line_height_px end -- -- if text is shorter than one line, shrink to text's width @@ -209,7 +262,7 @@ function TextBoxWidget:_findCharPos() local x = 0 local offset = self.vertical_string_list[ln].offset while offset < self.charpos do - x = x + self.char_width_list[offset].width + x = x + self.char_width_list[offset].width + self.char_width_list[offset].pad offset = offset + 1 end return x + 1, (ln - 1) * self.line_height_px -- offset `x` by 1 to avoid overlap @@ -238,11 +291,11 @@ function TextBoxWidget:moveCursor(x, y) local offset = self.vertical_string_list[ln].offset local idx = ln == #self.vertical_string_list and #self.char_width_list or self.vertical_string_list[ln + 1].offset - 1 while offset <= idx do - w = w + self.char_width_list[offset].width + w = w + self.char_width_list[offset].width + self.char_width_list[offset].pad if w > x then break else offset = offset + 1 end end if w > x then - local w_prev = w - self.char_width_list[offset].width + local w_prev = w - self.char_width_list[offset].width - self.char_width_list[offset].pad if x - w_prev < w - x then -- the previous one is more closer w = w_prev end @@ -331,7 +384,7 @@ function TextBoxWidget:onHoldWord(callback, ges) while idx < char_end do local c = self.char_width_list[idx] -- FIXME: this might break if kerning is enabled - char_probe_x = char_probe_x + c.width + char_probe_x = char_probe_x + c.width + c.pad if char_probe_x > x then -- ignore spaces if c.char == " " then break end @@ -456,7 +509,7 @@ function TextBoxWidget:_findWordEdge(x, y, side) -- find which character the touch is holding while idx < char_end do local c = self.char_width_list[idx] - char_probe_x = char_probe_x + c.width + char_probe_x = char_probe_x + c.width + c.pad if char_probe_x > x then -- character found, find which word the character is in, and -- get its start/end idx