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, } }