mirror of
https://github.com/koreader/koreader.git
synced 2025-12-24 12:14:05 +01:00
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)
142 lines
5.2 KiB
Lua
142 lines
5.2 KiB
Lua
--[[--
|
|
A TextWidget puts a string on a single line.
|
|
|
|
Example:
|
|
|
|
UIManager:show(TextWidget:new{
|
|
text = "Make it so.",
|
|
face = Font:getFace("cfont"),
|
|
bold = true,
|
|
fgcolor = Blitbuffer.COLOR_DARK_GRAY,
|
|
})
|
|
|
|
--]]
|
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local Geom = require("ui/geometry")
|
|
local Math = require("optmath")
|
|
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 = false, -- synthetized/fake bold (use a bold face for nicer bold)
|
|
fgcolor = Blitbuffer.COLOR_BLACK,
|
|
padding = Size.padding.small, -- vertical padding (should it be function of face.size ?)
|
|
-- (no horizontal padding is added)
|
|
max_width = 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:updateSize()
|
|
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
|
|
-- But better compute baseline alignment from freetype font metrics
|
|
-- to get better vertical centering of text in box
|
|
-- (Freetype doc on this at https://www.freetype.org/freetype2/docs/tutorial/step2.html)
|
|
local face_height, face_ascender = self.face.ftface:getHeightAndAscender()
|
|
self._height = math.ceil(face_height) + 2*self.padding
|
|
self._baseline_h = Math.round(face_ascender) + self.padding
|
|
-- With our UI fonts, this usually gives 0.72 to 0.74, so text is aligned
|
|
-- a bit lower than before with the hardcoded 0.7
|
|
-- require("logger").warn("1.5*face.size:", self.face.size * 1.5, "face_height:", face_height, "self._height:", self._height)
|
|
-- require("logger").warn("self._height ratio:", 1.0*self._baseline_h/self._height)
|
|
end
|
|
|
|
function TextWidget:getSize()
|
|
self:updateSize()
|
|
return Geom:new{
|
|
w = self._length,
|
|
h = self._height,
|
|
}
|
|
end
|
|
|
|
function TextWidget:getWidth()
|
|
self:updateSize()
|
|
return self._length
|
|
end
|
|
|
|
function TextWidget:getBaseline()
|
|
self:updateSize()
|
|
return self._baseline_h
|
|
end
|
|
|
|
function TextWidget:setText(text)
|
|
self.text = text
|
|
self._updated = false
|
|
end
|
|
|
|
function TextWidget:setMaxWidth(max_width)
|
|
self.max_width = max_width
|
|
self._updated = false
|
|
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
|