From f05e62c1fb4c8b75669c9139cb3281769cd43580 Mon Sep 17 00:00:00 2001 From: poire-z Date: Mon, 21 Oct 2019 15:20:40 +0200 Subject: [PATCH] TextWidget: small refactoring, better handle max_width (#5503) Lots of code was doing some renderText calls to get the size of some text string, and truncate it to some width if needed, with or without an added ellipsis, before instantiating a TextWidget with that tweaked text string. This PR fixes/adds some properties and methods to TextWidget so all that can be done by it. It makes the calling code simpler, as they don't need to use RenderText directly. (Additionally, when we go at using Harfbuzz for text rendering, we'll just have to update or replace textwidget.lua without the need to update any higher level code.) Also: - RenderText: removed the space added by truncateTextByWidth after the ellipsis, as it doesn't feel needed, and break right alignment of the ellipsis with other texts. - KeyValuePage: fix some subtle size and alignment issues. - NumberPickerWidget: fix font size (provided font size was not used) --- frontend/apps/filemanager/filemanager.lua | 10 +- frontend/ui/rendertext.lua | 21 +--- frontend/ui/screensaver.lua | 15 +-- frontend/ui/widget/configdialog.lua | 18 ++- frontend/ui/widget/fixedtextwidget.lua | 26 ++--- frontend/ui/widget/inputdialog.lua | 12 +- frontend/ui/widget/keyvaluepage.lua | 105 +++++++++++------ frontend/ui/widget/menu.lua | 103 ++++++++--------- frontend/ui/widget/numberpickerwidget.lua | 13 +-- frontend/ui/widget/sortwidget.lua | 18 +-- frontend/ui/widget/spinwidget.lua | 15 +-- frontend/ui/widget/textwidget.lua | 107 +++++++++++------- frontend/ui/widget/toggleswitch.lua | 10 +- frontend/ui/widget/touchmenu.lua | 5 +- frontend/ui/widget/trapwidget.lua | 15 +-- plugins/coverbrowser.koplugin/covermenu.lua | 2 +- .../goodreads.koplugin/doublekeyvaluepage.lua | 34 ++---- 17 files changed, 245 insertions(+), 284 deletions(-) diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 84a394125..0490565cd 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -50,10 +50,6 @@ local function restoreScreenMode() end end -local function truncatePath(text) - return FileChooser:truncatePath(text) -end - local FileManager = InputContainer:extend{ title = _("KOReader"), root_path = lfs.currentdir(), @@ -110,7 +106,9 @@ function FileManager:init() self.path_text = TextWidget:new{ face = Font:getFace("xx_smallinfofont"), - text = truncatePath(filemanagerutil.abbreviate(self.root_path)), + text = filemanagerutil.abbreviate(self.root_path), + max_width = Screen:getWidth() - 2*Size.padding.small, + truncate_left = true, } self.banner = FrameContainer:new{ @@ -177,7 +175,7 @@ function FileManager:init() self.focused_file = nil -- use it only once function file_chooser:onPathChanged(path) -- luacheck: ignore - FileManager.instance.path_text:setText(truncatePath(filemanagerutil.abbreviate(path))) + FileManager.instance.path_text:setText(filemanagerutil.abbreviate(path)) UIManager:setDirty(FileManager.instance, function() return "ui", FileManager.instance.path_text.dimen, FileManager.instance.dithered end) diff --git a/frontend/ui/rendertext.lua b/frontend/ui/rendertext.lua index 33c17a98b..a975ef10a 100644 --- a/frontend/ui/rendertext.lua +++ b/frontend/ui/rendertext.lua @@ -263,8 +263,7 @@ function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bo return pen_x end -local ellipsis, space = "…", " " -local ellipsis_width, space_width +local ellipsis = "…" --- Returns a substring of a given text that meets the maximum width (in pixels) -- restriction with ellipses (…) at the end if required. -- @@ -273,23 +272,13 @@ local ellipsis_width, space_width -- @int width maximum width in pixels -- @bool[opt=false] kerning whether the text should be measured with kerning -- @bool[opt=false] bold whether the text should be measured as bold --- @bool[opt=false] prepend_space whether a space should be prepended to the text -- @treturn string -- @see getSubTextByWidth -function RenderText:truncateTextByWidth(text, face, max_width, kerning, bold, prepend_space) - if not ellipsis_width then - ellipsis_width = self:sizeUtf8Text(0, max_width, face, ellipsis).x - end - if not space_width then - space_width = self:sizeUtf8Text(0, max_width, face, space).x - end - local new_txt_width = max_width - ellipsis_width - space_width +function RenderText:truncateTextByWidth(text, face, max_width, kerning, bold) + local ellipsis_width = self:sizeUtf8Text(0, max_width, face, ellipsis, false, bold).x + local new_txt_width = max_width - ellipsis_width local sub_txt = self:getSubTextByWidth(text, face, new_txt_width, kerning, bold) - if prepend_space then - return space.. sub_txt .. ellipsis - else - return sub_txt .. ellipsis .. space - end + return sub_txt .. ellipsis end return RenderText diff --git a/frontend/ui/screensaver.lua b/frontend/ui/screensaver.lua index f85426429..fc6db9896 100644 --- a/frontend/ui/screensaver.lua +++ b/frontend/ui/screensaver.lua @@ -459,7 +459,6 @@ function Screensaver:addOverlayMessage(widget, text) local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") local OverlapGroup = require("ui/widget/overlapgroup") - local RenderText = require("ui/rendertext") local RightContainer = require("ui/widget/container/rightcontainer") local Size = require("ui/size") local TextBoxWidget = require("ui/widget/textboxwidget") @@ -468,15 +467,13 @@ function Screensaver:addOverlayMessage(widget, text) local face = Font:getFace("infofont") local screen_w, screen_h = Screen:getWidth(), Screen:getHeight() - local textw + local textw = TextWidget:new{ + text = text, + face = face, + } -- Don't make our message reach full screen width - local tsize = RenderText:sizeUtf8Text(0, screen_w, face, text) - if tsize.x < screen_w * 0.9 then - textw = TextWidget:new{ - text = text, - face = face, - } - else -- if text too wide, use TextBoxWidget for multi lines display + if textw:getWidth() > screen_w * 0.9 then + -- Text too wide: use TextBoxWidget for multi lines display textw = TextBoxWidget:new{ text = text, face = face, diff --git a/frontend/ui/widget/configdialog.lua b/frontend/ui/widget/configdialog.lua index 3de2698b1..412018a12 100644 --- a/frontend/ui/widget/configdialog.lua +++ b/frontend/ui/widget/configdialog.lua @@ -18,7 +18,6 @@ local IconButton = require("ui/widget/iconbutton") local ImageWidget = require("ui/widget/imagewidget") local InputContainer = require("ui/widget/container/inputcontainer") local LineWidget = require("ui/widget/linewidget") -local RenderText = require("ui/rendertext") local RightContainer = require("ui/widget/container/rightcontainer") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") @@ -208,7 +207,11 @@ function ConfigOption:init() local face = Font:getFace(name_font_face, name_font_size) local txt_width = 0 if text ~= nil then - txt_width = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text).x + local tmp = TextWidget:new{ + text = text, + face = face, + } + txt_width = tmp:getWidth() end max_option_name_width = math.max(max_option_name_width, txt_width) end @@ -265,16 +268,12 @@ function ConfigOption:init() local name_text_max_width = name_widget_width - default_option_hpadding - 2*padding_small local text = self.options[c].name_text local face = Font:getFace(name_font_face, name_font_size) - local width_name_text = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text).x - if width_name_text > name_text_max_width then - text = RenderText:truncateTextByWidth(text, face, name_text_max_width) - end - local option_name_container = RightContainer:new{ dimen = Geom:new{ w = name_widget_width, h = option_height}, } local option_name = Button:new{ text = text, + max_width = name_text_max_width, bordersize = 0, face = face, enabled = enabled, @@ -436,13 +435,10 @@ function ConfigOption:init() else local text = self.options[c].item_text[d] local face = Font:getFace(item_font_face, item_font_size) - local width_item_text = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text).x - if max_item_text_width < width_item_text then - text = RenderText:truncateTextByWidth(text, face, max_item_text_width) - end option_item = OptionTextItem:new{ TextWidget:new{ text = text, + max_width = max_item_text_width, face = face, fgcolor = enabled and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY, }, diff --git a/frontend/ui/widget/fixedtextwidget.lua b/frontend/ui/widget/fixedtextwidget.lua index 31dc3aaae..b4ba9d0dd 100644 --- a/frontend/ui/widget/fixedtextwidget.lua +++ b/frontend/ui/widget/fixedtextwidget.lua @@ -1,29 +1,25 @@ local TextWidget = require("ui/widget/textwidget") -local RenderText = require("ui/rendertext") local Geom = require("ui/geometry") -local Screen = require("device").screen --[[ FixedTextWidget --]] local FixedTextWidget = TextWidget:new{} -function FixedTextWidget:getSize() - local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true, self.bold) - if tsize.x == 0 then - return Geom:new{} - end - self._length = tsize.x +function FixedTextWidget:updateSize() + TextWidget.updateSize(self) + -- Only difference from TextWidget: + -- no vertical padding, baseline is height self._height = self.face.size - return Geom:new{ - w = self._length, - h = self._height, - } + self._baseline_h = self.face.size end -function FixedTextWidget:paintTo(bb, x, y) - RenderText:renderUtf8Text(bb, x, y+self._height, self.face, self.text, true, self.bold, - self.fgcolor) +function FixedTextWidget:getSize() + self:updateSize() + if self._length == 0 then + return Geom:new{} + end + return TextWidget.getSize(self) end return FixedTextWidget diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index b35090771..b7c1b7445 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -108,7 +108,6 @@ local LineWidget = require("ui/widget/linewidget") local MovableContainer = require("ui/widget/container/movablecontainer") local MultiConfirmBox = require("ui/widget/multiconfirmbox") local Notification = require("ui/widget/notification") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextBoxWidget = require("ui/widget/textboxwidget") local TextWidget = require("ui/widget/textwidget") @@ -211,15 +210,6 @@ function InputDialog:init() end -- Title & description - local title_width = RenderText:sizeUtf8Text(0, self.width, - self.title_face, self.title, true).x - if title_width > self.width then - local indicator = " >> " - local indicator_w = RenderText:sizeUtf8Text(0, self.width, - self.title_face, indicator, true).x - self.title = RenderText:getSubTextByWidth(self.title, self.title_face, - self.width - indicator_w, true) .. indicator - end self.title_widget = FrameContainer:new{ padding = self.title_padding, margin = self.title_margin, @@ -227,7 +217,7 @@ function InputDialog:init() TextWidget:new{ text = self.title, face = self.title_face, - width = self.width, + max_width = self.width, } } self.title_bar = LineWidget:new{ diff --git a/frontend/ui/widget/keyvaluepage.lua b/frontend/ui/widget/keyvaluepage.lua index a0280833a..d023e6c7c 100644 --- a/frontend/ui/widget/keyvaluepage.lua +++ b/frontend/ui/widget/keyvaluepage.lua @@ -34,7 +34,6 @@ local InputContainer = require("ui/widget/container/inputcontainer") local LeftContainer = require("ui/widget/container/leftcontainer") local LineWidget = require("ui/widget/linewidget") local OverlapGroup = require("ui/widget/overlapgroup") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextViewer = require("ui/widget/textviewer") local TextWidget = require("ui/widget/textwidget") @@ -57,20 +56,12 @@ local KeyValueTitle = VerticalGroup:new{ function KeyValueTitle:init() self.close_button = CloseButton:new{ window = self } local btn_width = self.close_button:getSize().w - local title_txt_width = RenderText:sizeUtf8Text( - 0, self.width, self.tface, self.title).x - local show_title_txt - if self.width < (title_txt_width + btn_width) then - show_title_txt = RenderText:truncateTextByWidth( - self.title, self.tface, self.width-btn_width) - else - show_title_txt = self.title - end -- title and close button table.insert(self, OverlapGroup:new{ dimen = { w = self.width }, TextWidget:new{ - text = show_title_txt, + text = self.title, + max_width = self.width - btn_width, face = self.tface, }, self.close_button, @@ -154,25 +145,54 @@ function KeyValueItem:init() local frame_padding = Size.padding.default local frame_internal_width = self.width - frame_padding * 2 + -- Default widths (and position of value widget) if each text fits in 1/2 screen width local key_w = frame_internal_width / 2 local value_w = frame_internal_width / 2 - local key_w_rendered = RenderText:sizeUtf8Text(0, frame_internal_width, self.tface, self.key).x - local value_w_rendered = RenderText:sizeUtf8Text(0, frame_internal_width, self.cface, tvalue).x - local space_w_rendered = RenderText:sizeUtf8Text(0, frame_internal_width, self.cface, " ").x + + local key_widget = TextWidget:new{ + text = self.key, + max_width = frame_internal_width, + face = self.tface, + } + local value_widget = TextWidget:new{ + text = tvalue, + max_width = frame_internal_width, + face = self.cface, + } + local key_w_rendered = key_widget:getWidth() + local value_w_rendered = value_widget:getWidth() + + -- As both key_widget and value_width will be in a HorizontalGroup, + -- and key is always left aligned, we can just tweak the key width + -- to position the value_widget + local value_prepend_space = false + local value_align_right = false + local fit_right_align = true -- by default, really right align + if key_w_rendered > key_w or value_w_rendered > value_w then - -- truncate key or value so they fit in one row + -- One (or both) does not fit in 1/2 width if key_w_rendered + value_w_rendered > frame_internal_width then + -- Both do not fit: one has to be truncated so they fit if key_w_rendered >= value_w_rendered then + -- Rare case: key larger than value. + -- We should have kept our keys small, smaller than 1/2 width. + -- If it is larger than value, it's that value is kinda small, + -- so keep the whole value, and truncate the key key_w = frame_internal_width - value_w_rendered - self.show_key = RenderText:truncateTextByWidth(self.key, self.tface, frame_internal_width - value_w_rendered) - self.show_value = tvalue else - key_w = key_w_rendered + space_w_rendered - self.show_value = RenderText:truncateTextByWidth(tvalue, self.cface, frame_internal_width - key_w_rendered, - false, false, true) - self.show_key = self.key + -- Usual case: value larger than key. + -- Keep our small key, fit the value in the remaining width, + -- prepend some space to separate them + key_w = key_w_rendered + value_prepend_space = true end - -- allow for displaying the non-truncated texts with Hold + value_align_right = true -- so the ellipsis touches the screen right border + if self.value_overflow_align ~= "right" and self.value_align ~= "right" then + -- Don't adjust the ellipsis to the screen right border, + -- so the left of text is aligned with other truncated texts + fit_right_align = false + end + -- Allow for displaying the non-truncated texts with Hold if Device:isTouchDevice() then self.ges_events.Hold = { GestureRange:new{ @@ -181,24 +201,43 @@ function KeyValueItem:init() } } end - -- misalign to fit all info else + -- Both can fit: break the 1/2 widths if self.value_overflow_align == "right" or self.value_align == "right" then key_w = frame_internal_width - value_w_rendered + value_align_right = true else - key_w = key_w_rendered + space_w_rendered + key_w = key_w_rendered + value_prepend_space = true end - self.show_key = self.key - self.show_value = tvalue end + -- In all the above case, we set the right key_w to include any + -- needed in-between padding: value_w is what's left. + value_w = frame_internal_width - key_w else if self.value_align == "right" then key_w = frame_internal_width - value_w_rendered + value_w = value_w_rendered + value_align_right = true end - self.show_key = self.key - self.show_value = tvalue end + -- Adjust widgets' max widths and text as needed + if value_prepend_space then + value_widget:setText(" "..tvalue) + end + value_widget:setMaxWidth(value_w) + if fit_right_align and value_align_right and value_widget:getWidth() < value_w then + -- Because of truncation at glyph boundaries, value_widget + -- may be a tad smaller than the specified value_w: + -- add some padding to key_w so value is pushed to the screen right border + key_w = key_w + ( value_w - value_widget:getWidth() ) + end + key_widget:setMaxWidth(key_w) + + -- For debugging positioning: + -- value_widget = FrameContainer:new{ padding=0, margin=0, bordersize=1, value_widget } + self[1] = FrameContainer:new{ padding = frame_padding, bordersize = 0, @@ -209,20 +248,14 @@ function KeyValueItem:init() w = key_w, h = self.height }, - TextWidget:new{ - text = self.show_key, - face = self.tface, - } + key_widget, }, LeftContainer:new{ dimen = { w = value_w, h = self.height }, - TextWidget:new{ - text = self.show_value, - face = self.cface, - } + value_widget, } } } diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index ae0ce3033..321649821 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -129,8 +129,6 @@ local MenuItem = InputContainer:new{ text = nil, show_parent = nil, detail = nil, - face = Font:getFace("cfont", 30), - info_face = Font:getFace("infont", 15), font = "cfont", font_size = 24, infont = "infont", @@ -176,6 +174,25 @@ function MenuItem:init() } end + -- State button and indentation for tree expand/collapse (for TOC) + local state_button_width = self.state_size.w or 0 + local state_button = self.state or HorizontalSpan:new{ + width = state_button_width, + } + local state_indent = self.state and self.state.indent or "" + local state_container = LeftContainer:new{ + dimen = Geom:new{w = self.content_width/2, h = self.dimen.h}, + HorizontalGroup:new{ + TextWidget:new{ + text = state_indent, + face = Font:getFace(self.font, self.font_size), + }, + state_button, + } + } + + -- "mandatory" is the text on the right: file size, page number... + -- Padding before mandatory local text_mandatory_padding = 0 local text_ellipsis_mandatory_padding = 0 if self.mandatory then @@ -185,44 +202,39 @@ function MenuItem:init() end local mandatory = self.mandatory and ""..self.mandatory or "" - local state_button_width = self.state_size.w or 0 - local state_button = self.state or HorizontalSpan:new{ - width = state_button_width, - } - local state_indent = self.state and self.state.indent or "" + -- Font for main text (may have its size decreased to make text fit) + self.face = Font:getFace(self.font, self.font_size) + -- Font for "mandatory" on the right + self.info_face = Font:getFace(self.infont, self.infont_size) + local item_name local mandatory_widget if self.single_line then -- items only in single line - self.info_face = Font:getFace(self.infont, self.infont_size) - self.face = Font:getFace(self.font, self.font_size) - - local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, ""..mandatory, true, self.bold).x - - local my_text = self.text and ""..self.text or "" - local w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, my_text, true, self.bold).x - if w + mandatory_w + state_button_width + text_mandatory_padding >= self.content_width then - local indicator = "\226\128\166 " -- an ellipsis - local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, - indicator, true, self.bold).x - self.text = RenderText:getSubTextByWidth(my_text, self.face, - self.content_width - indicator_w - mandatory_w - state_button_width - text_ellipsis_mandatory_padding, - true, self.bold) .. indicator - end - - item_name = TextWidget:new{ - text = self.text, - face = self.face, - bold = self.bold, - fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil, - } mandatory_widget = TextWidget:new{ text = mandatory, face = self.info_face, bold = self.bold, fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil, } - if self.align_baselines then + local mandatory_w = mandatory_widget:getWidth() + -- No font size change: text will be truncated if it overflows + -- (we give it a little more room if truncated for better visual + -- feeling - which might make it no more truncated, but well...) + local text_max_width_base = self.content_width - state_button_width - mandatory_w + local text_max_width = text_max_width_base - text_mandatory_padding + local text_max_width_if_ellipsis = text_max_width_base - text_ellipsis_mandatory_padding + item_name = TextWidget:new{ + text = self.text, + face = self.face, + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil, + } + local w = item_name:getWidth() + if w > text_max_width then + item_name:setMaxWidth(text_max_width_if_ellipsis) + end + if self.align_baselines then -- Align baselines of text and mandatory local name_baseline = item_name:getBaseline() local mandatory_baseline = mandatory_widget:getBaseline() local baselines_diff = Math.round(name_baseline - mandatory_baseline) @@ -313,16 +325,6 @@ function MenuItem:init() self.face = Font:getFace(self.font, self.font_size) end - local state_container = LeftContainer:new{ - dimen = Geom:new{w = self.content_width/2, h = self.dimen.h}, - HorizontalGroup:new{ - HorizontalSpan:new{ - width = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, - state_indent, true, self.bold).x, - }, - state_button, - } - } local text_container = LeftContainer:new{ dimen = Geom:new{w = self.content_width, h = self.dimen.h}, HorizontalGroup:new{ @@ -579,7 +581,9 @@ function Menu:init() if self.show_path then self.path_text = TextWidget:new{ face = Font:getFace("xx_smallinfofont"), - text = self:truncatePath(self.path), + text = self.path, + max_width = self.dimen.w - 2*Size.padding.small, + truncate_left = true, } path_text_container = CenterContainer:new{ dimen = Geom:new{ @@ -865,19 +869,6 @@ function Menu:init() end end -function Menu:truncatePath(text) - local screen_width = Screen:getWidth() - local face = Font:getFace("xx_smallinfofont") - -- we want to truncate text on the left, so work with the reverse of text (which is fine as we don't use kerning) - local reversed_text = require("util").utf8Reverse(text) - local txt_width = RenderText:sizeUtf8Text(0, screen_width, face, reversed_text, false, false).x - if screen_width - 2 * Size.padding.small < txt_width then - reversed_text = RenderText:truncateTextByWidth(reversed_text, face, screen_width - 2 * Size.padding.small, false, false) - text = require("util").utf8Reverse(reversed_text) - end - return text -end - function Menu:onCloseWidget() --- @fixme -- we cannot refresh regionally using the dimen field @@ -984,7 +975,7 @@ function Menu:updateItems(select_number) self:updatePageInfo(select_number) if self.show_path then - self.path_text.text = self:truncatePath(self.path) + self.path_text:setText(self.path) end UIManager:setDirty(self.show_parent, function() @@ -1011,7 +1002,7 @@ end --]] function Menu:switchItemTable(new_title, new_item_table, itemnumber, itemmatch) if self.menu_title and new_title then - self.menu_title.text = new_title + self.menu_title:setText(new_title) end if itemnumber == nil then diff --git a/frontend/ui/widget/numberpickerwidget.lua b/frontend/ui/widget/numberpickerwidget.lua index b0e626775..bd25ef0db 100644 --- a/frontend/ui/widget/numberpickerwidget.lua +++ b/frontend/ui/widget/numberpickerwidget.lua @@ -25,7 +25,6 @@ local Font = require("ui/font") local InfoMessage = require("ui/widget/infomessage") local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") @@ -120,12 +119,7 @@ function NumberPickerWidget:paintWidget() width = self.screen_height * 0.01 } local value = self.value - if self.value_table then - local text_width = RenderText:sizeUtf8Text(0, self.width, self.spinner_face, self.value, true, true).x - if self.width < text_width then - value = RenderText:truncateTextByWidth(self.value, self.spinner_face, self.width,true, true) - end - else + if not self.value_table then value = string.format(self.precision, value) end @@ -185,9 +179,10 @@ function NumberPickerWidget:paintWidget() text = value, bordersize = 0, padding = 0, - text_font_face = self.spinner_face_font, - text_font_size = self.spinner_face_size, + text_font_face = self.spinner_face.font, + text_font_size = self.spinner_face.orig_size, width = self.width, + max_width = self.width, callback = callback_input, } return VerticalGroup:new{ diff --git a/frontend/ui/widget/sortwidget.lua b/frontend/ui/widget/sortwidget.lua index 4923431dc..ba4a740c8 100644 --- a/frontend/ui/widget/sortwidget.lua +++ b/frontend/ui/widget/sortwidget.lua @@ -12,7 +12,6 @@ local InputContainer = require("ui/widget/container/inputcontainer") local LeftContainer = require("ui/widget/container/leftcontainer") local LineWidget = require("ui/widget/linewidget") local OverlapGroup = require("ui/widget/overlapgroup") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") @@ -33,20 +32,12 @@ local SortTitleWidget = VerticalGroup:new{ function SortTitleWidget:init() self.close_button = CloseButton:new{ window = self } local btn_width = self.close_button:getSize().w - local title_txt_width = RenderText:sizeUtf8Text( - 0, self.width, self.tface, self.title).x - local show_title_txt - if self.width < (title_txt_width + btn_width) then - show_title_txt = RenderText:truncateTextByWidth( - self.title, self.tface, self.width-btn_width) - else - show_title_txt = self.title - end -- title and close button table.insert(self, OverlapGroup:new{ dimen = { w = self.width }, TextWidget:new{ - text = show_title_txt, + text = self.title, + max_width = self.width - btn_width, face = self.tface, }, self.close_button, @@ -124,10 +115,6 @@ function SortItemWidget:init() local frame_padding = Size.padding.default local frame_internal_width = self.width - frame_padding * 2 - local text_rendered = RenderText:sizeUtf8Text(0, self.width, self.tface, self.text).x - if text_rendered > frame_internal_width then - self.text = RenderText:truncateTextByWidth(self.text, self.tface, frame_internal_width) - end self[1] = FrameContainer:new{ padding = 0, @@ -139,6 +126,7 @@ function SortItemWidget:init() }, TextWidget:new{ text = self.text, + max_width = frame_internal_width, face = self.tface, } }, diff --git a/frontend/ui/widget/spinwidget.lua b/frontend/ui/widget/spinwidget.lua index 9aa061789..8f46f5d37 100644 --- a/frontend/ui/widget/spinwidget.lua +++ b/frontend/ui/widget/spinwidget.lua @@ -12,7 +12,6 @@ local InputContainer = require("ui/widget/container/inputcontainer") local LineWidget = require("ui/widget/linewidget") local NumberPickerWidget = require("ui/widget/numberpickerwidget") local OverlapGroup = require("ui/widget/overlapgroup") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextBoxWidget = require("ui/widget/textboxwidget") local TextWidget = require("ui/widget/textwidget") @@ -90,24 +89,16 @@ function SpinWidget:update() local close_button = CloseButton:new{ window = self, padding_top = Size.margin.title, } local btn_width = close_button:getSize().w + Size.padding.default * 2 - local title_txt_width = RenderText:sizeUtf8Text( - 0, self.width, self.title_face, self.title_text).x - local show_title_txt - if self.width < (title_txt_width + btn_width) then - show_title_txt = RenderText:truncateTextByWidth( - self.title_text, self.title_face, self.width - btn_width) - else - show_title_txt = self.title_text - end + local value_title = FrameContainer:new{ padding = Size.padding.default, margin = Size.margin.title, bordersize = 0, TextWidget:new{ - text = show_title_txt, + text = self.title_text, + max_width = self.width - btn_width, face = self.title_face, bold = true, - width = self.width, }, } local value_line = LineWidget:new{ diff --git a/frontend/ui/widget/textwidget.lua b/frontend/ui/widget/textwidget.lua index f1292902f..b3b4a3095 100644 --- a/frontend/ui/widget/textwidget.lua +++ b/frontend/ui/widget/textwidget.lua @@ -19,39 +19,76 @@ local RenderText = require("ui/rendertext") local Size = require("ui/size") local Widget = require("ui/widget/widget") local Screen = require("device").screen +local util = require("util") local TextWidget = Widget:new{ text = nil, face = nil, - bold = nil, + bold = false, -- synthetized/fake bold (use a bold face for nicer bold) fgcolor = Blitbuffer.COLOR_BLACK, - padding = Size.padding.small, -- should padding be function of face.size ? + padding = Size.padding.small, -- vertical padding (should it be function of face.size ?) + -- (no horizontal padding is added) max_width = nil, - _bb = nil, + truncate_with_ellipsis = true, -- when truncation at max_width needed, add "…" + truncate_left = false, -- truncate on the right by default + + _updated = nil, + _text_to_draw = nil, _length = 0, _height = 0, _baseline_h = 0, _maxlength = 1200, } ---function TextWidget:_render() - --local h = self.face.size * 1.3 - --self._bb = Blitbuffer.new(self._maxlength, h) - --self._bb:fill(Blitbuffer.COLOR_WHITE) - --self._length = RenderText:renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, true, self.bold) ---end - function TextWidget:updateSize() - local tsize = RenderText:sizeUtf8Text(0, self.max_width and self.max_width or Screen:getWidth(), self.face, self.text, true, self.bold) - if tsize.x == 0 then - self._length = 0 - else - -- As text length includes last glyph pen "advance" (for positionning - -- next char), it's best to use math.floor() instead of math.ceil() - -- to get rid of a fraction of it in case this text is to be - -- horizontally centered + if self._updated then + return + end + self._updated = true + + -- In case we draw truncated text, keep original self.text + -- so caller can fetch it again + self._text_to_draw = self.text + + -- Note: we use kerning=true in all RenderText calls + --- @todo Don't use kerning for monospaced fonts. (houqp) + + -- Compute width: + -- We never need to draw/size more than one screen width, so limit computation + -- to that width in case we are given some huge string + local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self._text_to_draw, true, self.bold) + -- As text length includes last glyph pen "advance" (for positionning + -- next char), it's best to use math.floor() instead of math.ceil() + -- to get rid of a fraction of it in case this text is to be + -- horizontally centered + self._length = math.floor(tsize.x) + + -- Ensure max_width, and truncate text if needed + if self.max_width and self._length > self.max_width then + if self.truncate_left then + -- We want to truncate text on the left, so work with the reverse of text. + -- We don't use kerning in this measurement as it might be different + -- on the reversed text. The final text will use kerning, and might get + -- a smaller width than the one found out here. + -- Also, not sure if this is correct when diacritics/clustered glyphs + -- happen at truncation point. But it will do for now. + local reversed_text = util.utf8Reverse(self._text_to_draw) + if self.truncate_with_ellipsis then + reversed_text = RenderText:truncateTextByWidth(reversed_text, self.face, self.max_width, false, self.bold) + else + reversed_text = RenderText:getSubTextByWidth(reversed_text, self.face, self.max_width, false, self.bold) + end + self._text_to_draw = util.utf8Reverse(reversed_text) + elseif self.truncate_with_ellipsis then + self._text_to_draw = RenderText:truncateTextByWidth(self._text_to_draw, self.face, self.max_width, true, self.bold) + end + -- Get the adjusted width when limiting to max_width (it might be + -- smaller than max_width when dropping the truncated glyph). + tsize = RenderText:sizeUtf8Text(0, self.max_width, self.face, self._text_to_draw, true, self.bold) self._length = math.floor(tsize.x) end + + -- Compute height: -- Used to be: -- self._height = math.ceil(self.face.size * 1.5) -- self._baseline_h = self._height*0.7 @@ -68,10 +105,6 @@ function TextWidget:updateSize() end function TextWidget:getSize() - --if not self._bb then - --self:_render() - --end - --return { w = self._length, h = self._bb:getHeight() } self:updateSize() return Geom:new{ w = self._length, @@ -79,6 +112,11 @@ function TextWidget:getSize() } end +function TextWidget:getWidth() + self:updateSize() + return self._length +end + function TextWidget:getBaseline() self:updateSize() return self._baseline_h @@ -86,27 +124,18 @@ end function TextWidget:setText(text) self.text = text - self:updateSize() + self._updated = false end -function TextWidget:paintTo(bb, x, y) - --if not self._bb then - --self:_render() - --end - --bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight()) - --- @todo Don't use kerning for monospaced fonts. (houqp) - if self.max_width and RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true, self.bold).x > self.max_width then - self.text = RenderText:truncateTextByWidth(self.text, self.face, self.max_width, true) - end - RenderText:renderUtf8Text(bb, x, y+self._baseline_h, self.face, self.text, true, self.bold, - self.fgcolor, self.max_width and self.max_width or self.width) +function TextWidget:setMaxWidth(max_width) + self.max_width = max_width + self._updated = false end -function TextWidget:free() - if self._bb then - self._bb:free() - self._bb = nil - end +function TextWidget:paintTo(bb, x, y) + self:updateSize() + RenderText:renderUtf8Text(bb, x, y+self._baseline_h, self.face, self._text_to_draw, true, self.bold, + self.fgcolor, self._length) end return TextWidget diff --git a/frontend/ui/widget/toggleswitch.lua b/frontend/ui/widget/toggleswitch.lua index e61b2e41d..bbdedeefe 100644 --- a/frontend/ui/widget/toggleswitch.lua +++ b/frontend/ui/widget/toggleswitch.lua @@ -14,7 +14,6 @@ local GestureRange = require("ui/gesturerange") local HorizontalGroup = require("ui/widget/horizontalgroup") local InputContainer = require("ui/widget/container/inputcontainer") local FrameContainer = require("ui/widget/container/framecontainer") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") @@ -28,10 +27,6 @@ local ToggleLabel = TextWidget:new{ fgcolor = Blitbuffer.COLOR_BLACK, } -function ToggleLabel:paintTo(bb, x, y) - RenderText:renderUtf8Text(bb, x, y+self._baseline_h, self.face, self.text, true, self.bold, self.fgcolor) -end - local ToggleSwitch = InputContainer:new{ width = Screen:scaleBySize(216), height = Size.item.height_default, @@ -84,13 +79,10 @@ function ToggleSwitch:init() end local text = self.toggle[i] local face = Font:getFace(self.font_face, self.font_size) - local txt_width = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text, true, true).x - if txt_width > real_item_width - item_padding then - text = RenderText:truncateTextByWidth(text, face, real_item_width - item_padding, true, true) - end local label = ToggleLabel:new{ text = text, face = face, + max_width = real_item_width - item_padding, } local content = CenterContainer:new{ dimen = Geom:new{ diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index 765605c21..556a43601 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -19,7 +19,6 @@ local InfoMessage = require("ui/widget/infomessage") local InputContainer = require("ui/widget/container/inputcontainer") local LeftContainer = require("ui/widget/container/leftcontainer") local LineWidget = require("ui/widget/linewidget") -local RenderText = require("ui/rendertext") local RightContainer = require("ui/widget/container/rightcontainer") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") @@ -87,9 +86,6 @@ function TouchMenuItem:init() -- FrameContainer default paddings minus the checked widget width local text_max_width = self.dimen.w - 2*Size.padding.default - checked_widget:getSize().w local text = getMenuText(self.item) - if RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, text, true).x > text_max_width then - text = RenderText:truncateTextByWidth(text, self.face, text_max_width, true) - end self.item_frame = FrameContainer:new{ width = self.dimen.w, bordersize = 0, @@ -102,6 +98,7 @@ function TouchMenuItem:init() }, TextWidget:new{ text = text, + max_width = text_max_width, fgcolor = item_enabled ~= false and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY, face = self.face, }, diff --git a/frontend/ui/widget/trapwidget.lua b/frontend/ui/widget/trapwidget.lua index 74391159f..7dba76521 100644 --- a/frontend/ui/widget/trapwidget.lua +++ b/frontend/ui/widget/trapwidget.lua @@ -18,7 +18,6 @@ local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") local InputContainer = require("ui/widget/container/inputcontainer") local LeftContainer = require("ui/widget/container/leftcontainer") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextBoxWidget = require("ui/widget/textboxwidget") local TextWidget = require("ui/widget/textwidget") @@ -59,16 +58,14 @@ function TrapWidget:init() } end if self.text then - local textw + local textw = TextWidget:new{ + text = self.text, + face = self.face, + } -- Don't make our message reach full screen width, so -- it looks like popping from bottom left corner - local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text) - if tsize.x < Screen:getWidth() * 0.9 then - textw = TextWidget:new{ - text = self.text, - face = self.face, - } - else -- if text too wide, use TextBoxWidget for multi lines display + if textw:getWidth() > Screen:getWidth() * 0.9 then + -- Text too wide: use TextBoxWidget for multi lines display textw = TextBoxWidget:new{ text = self.text, face = self.face, diff --git a/plugins/coverbrowser.koplugin/covermenu.lua b/plugins/coverbrowser.koplugin/covermenu.lua index 80484d9a4..fd4bbf0b8 100644 --- a/plugins/coverbrowser.koplugin/covermenu.lua +++ b/plugins/coverbrowser.koplugin/covermenu.lua @@ -95,7 +95,7 @@ function CoverMenu:updateItems(select_number) self:updatePageInfo(select_number) if self.show_path then - self.path_text.text = self:truncatePath(self.path) + self.path_text:setText(self.path) end self.show_parent.dithered = self._has_cover_images UIManager:setDirty(self.show_parent, function() diff --git a/plugins/goodreads.koplugin/doublekeyvaluepage.lua b/plugins/goodreads.koplugin/doublekeyvaluepage.lua index 6e953c981..bebe29ec6 100644 --- a/plugins/goodreads.koplugin/doublekeyvaluepage.lua +++ b/plugins/goodreads.koplugin/doublekeyvaluepage.lua @@ -17,7 +17,6 @@ local LeftContainer = require("ui/widget/container/topcontainer") local LineWidget = require("ui/widget/linewidget") local LuaSettings = require("luasettings") local OverlapGroup = require("ui/widget/overlapgroup") -local RenderText = require("ui/rendertext") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") @@ -39,20 +38,12 @@ local DoubleKeyValueTitle = VerticalGroup:new{ function DoubleKeyValueTitle:init() self.close_button = CloseButton:new{ window = self } local btn_width = self.close_button:getSize().w - local title_txt_width = RenderText:sizeUtf8Text( - 0, self.width, self.tface, self.title).x - local show_title_txt - if self.width < (title_txt_width + btn_width) then - show_title_txt = RenderText:truncateTextByWidth( - self.title, self.tface, self.width - btn_width) - else - show_title_txt = self.title - end -- title and close button table.insert(self, OverlapGroup:new{ dimen = { w = self.width }, TextWidget:new{ - text = show_title_txt, + text = self.title, + max_width = self.width - btn_width, face = self.tface, }, self.close_button, @@ -114,7 +105,6 @@ local DoubleKeyValueItem = InputContainer:new{ function DoubleKeyValueItem:init() self.dimen = Geom:new{align = "left", w = self.width, h = self.height} - local padding = Screen:scaleBySize(20) if self.callback and Device:isTouchDevice() then self.ges_events.Tap = { GestureRange:new{ @@ -123,18 +113,8 @@ function DoubleKeyValueItem:init() } } end - local key_w = RenderText:sizeUtf8Text(0, self.width, self.cface_down, self.key).x - local value_w = RenderText:sizeUtf8Text(0, self.width, self.cface_up, self.value).x - if key_w > self.width - 2*padding then - self.show_key = RenderText:truncateTextByWidth(self.key, self.cface_down, self.width - 2*padding) - else - self.show_key = self.key - end - if value_w > self.width - 2*padding then - self.show_value = RenderText:truncateTextByWidth(self.value, self.cface_up, self.width - 2*padding) - else - self.show_value = self.value - end + local padding = Screen:scaleBySize(20) + local max_width = self.width - 2*padding local h = self.dimen.h / 2 local w = self.dimen.w self[1] = FrameContainer:new{ @@ -147,7 +127,8 @@ function DoubleKeyValueItem:init() padding = 0, dimen = Geom:new{ h = h, w = w }, TextWidget:new{ - text = self.show_value, + text = self.value, + max_width = max_width, face = self.cface_up, } }, @@ -155,7 +136,8 @@ function DoubleKeyValueItem:init() padding = 0, dimen = Geom:new{ h = h, w = w }, TextWidget:new{ - text = self.show_key, + text = self.key, + max_width = max_width, face = self.cface_down, } }