mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
Font rendering: add some helpers for use by xtext
bump base for libkoreader-xtext.so: - ffi/pic.lua: fix memory leak with some unsupported PNG files - FreeType ffi: add methods for use with Harfbuzz shaping - thirdparty: adds libunibreak - Adds libkoreader-xtext.so: enhanced text shaping Add a getFallbackFont(N) to each Lua font object to instantiate (if not yet done) and return the Nth fallback font for the font. Fallback fonts list: add NotoSansArabicUI-Regular.ttf Add RenderText:getGlyphByIndex() to get a glyph bitmap by glyph index, which is what we'll get from Harfbuzz shaping. (RenderText:getGlyph() works with unicode charcode).
This commit is contained in:
@@ -133,6 +133,7 @@ read_globals = {
|
||||
"cre",
|
||||
"lfs",
|
||||
"lipc",
|
||||
"xtext",
|
||||
}
|
||||
|
||||
exclude_files = {
|
||||
|
||||
2
base
2
base
Submodule base updated: c140b752e1...6c480198b0
@@ -79,9 +79,10 @@ local Font = {
|
||||
fallbacks = {
|
||||
[1] = "NotoSans-Regular.ttf",
|
||||
[2] = "NotoSansCJKsc-Regular.otf",
|
||||
[3] = "nerdfonts/symbols.ttf",
|
||||
[4] = "freefont/FreeSans.ttf",
|
||||
[5] = "freefont/FreeSerif.ttf",
|
||||
[3] = "NotoSansArabicUI-Regular.ttf",
|
||||
[4] = "nerdfonts/symbols.ttf",
|
||||
[5] = "freefont/FreeSans.ttf",
|
||||
[6] = "freefont/FreeSerif.ttf",
|
||||
},
|
||||
|
||||
-- face table
|
||||
@@ -92,6 +93,49 @@ if is_android then
|
||||
table.insert(Font.fallbacks, 3, "DroidSansFallback.ttf") -- for some ancient pre-4.4 Androids
|
||||
end
|
||||
|
||||
-- Synthetized bold strength can be tuned:
|
||||
-- local bold_strength_factor = 1 -- really too bold
|
||||
-- local bold_strength_factor = 1/2 -- bold enough
|
||||
local bold_strength_factor = 3/8 -- as crengine, lighter
|
||||
|
||||
-- Callback to be used by libkoreader-xtext.so to get Freetype
|
||||
-- instantiated fallback fonts when needed for shaping text
|
||||
local _getFallbackFont = function(face_obj, num)
|
||||
if not num or num == 0 then -- return the main font
|
||||
if not face_obj.embolden_half_strength then
|
||||
-- cache this value in case we use bold, to avoid recomputation
|
||||
face_obj.embolden_half_strength = face_obj.ftface:getEmboldenHalfStrength(bold_strength_factor)
|
||||
end
|
||||
return face_obj
|
||||
end
|
||||
if not face_obj.fallbacks then
|
||||
face_obj.fallbacks = {}
|
||||
end
|
||||
if face_obj.fallbacks[num] ~= nil then
|
||||
return face_obj.fallbacks[num]
|
||||
end
|
||||
local next_num = #face_obj.fallbacks + 1
|
||||
local cur_num = 0
|
||||
for index, fontname in pairs(Font.fallbacks) do
|
||||
if fontname ~= face_obj.realname then -- Skip base one if among fallbacks
|
||||
local fb_face = Font:getFace(fontname, face_obj.orig_size)
|
||||
if fb_face ~= nil then -- valid font
|
||||
cur_num = cur_num + 1
|
||||
if cur_num == next_num then
|
||||
face_obj.fallbacks[next_num] = fb_face
|
||||
if not fb_face.embolden_half_strength then
|
||||
fb_face.embolden_half_strength = fb_face.ftface:getEmboldenHalfStrength(bold_strength_factor)
|
||||
end
|
||||
return fb_face
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- no more fallback font
|
||||
face_obj.fallbacks[next_num] = false
|
||||
return false
|
||||
end
|
||||
|
||||
--- Gets font face object.
|
||||
-- @string font
|
||||
-- @int size optional size
|
||||
@@ -144,12 +188,34 @@ function Font:getFace(font, size)
|
||||
-- @field hash hash key for this font face
|
||||
face_obj = {
|
||||
orig_font = font,
|
||||
realname = realname,
|
||||
size = size,
|
||||
orig_size = orig_size,
|
||||
ftface = face,
|
||||
hash = hash
|
||||
hash = hash,
|
||||
}
|
||||
self.faces[hash] = face_obj
|
||||
|
||||
-- Callback to be used by libkoreader-xtext.so to get Freetype
|
||||
-- instantiated fallback fonts when needed for shaping text
|
||||
face_obj.getFallbackFont = function(num)
|
||||
return _getFallbackFont(face_obj, num)
|
||||
end
|
||||
-- Font features, to be given by libkoreader-xtext.so to HarfBuzz.
|
||||
-- (Could be tweaked by font if needed. Note that NotoSans does not
|
||||
-- have common ligatures, like for "fi" or "fl", so we won't see
|
||||
-- them in the UI.)
|
||||
-- Use HB defaults, and be sure to enable kerning and ligatures
|
||||
-- (which might be part of HB defaults, or not, not sure).
|
||||
face_obj.hb_features = { "+kern", "+liga" }
|
||||
-- If we'd wanted to disable all features that might be enabled
|
||||
-- by HarfBuzz (see harfbuzz/src/hb-ot-shape.cc, quite unclear
|
||||
-- what's enabled or not by default):
|
||||
-- face_obj.hb_features = {
|
||||
-- "-kern", "-mark", "-mkmk", "-curs", "-locl", "-liga",
|
||||
-- "-rlig", "-clig", "-ccmp", "-calt", "-rclt", "-rvrn",
|
||||
-- "-ltra", "-ltrm", "-rtla", "-rtlm", "-frac", "-numr",
|
||||
-- "-dnom", "-rand", "-trak", "-vert", }
|
||||
end
|
||||
return face_obj
|
||||
end
|
||||
|
||||
@@ -172,7 +172,7 @@ function RenderText:sizeUtf8Text(x, width, face, text, kerning, bold)
|
||||
local pen_y_bottom = 0
|
||||
local prevcharcode = 0
|
||||
for _, charcode, uchar in utf8Chars(text) do
|
||||
if pen_x < (width - x) then
|
||||
if not width or pen_x < (width - x) then
|
||||
local glyph = self:getGlyph(face, charcode, bold)
|
||||
if kerning and (prevcharcode ~= 0) then
|
||||
pen_x = pen_x + (face.ftface):getKerning(prevcharcode, charcode)
|
||||
@@ -264,6 +264,11 @@ function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bo
|
||||
end
|
||||
|
||||
local ellipsis = "…"
|
||||
|
||||
function RenderText:getEllipsisWidth(face, bold)
|
||||
return self:sizeUtf8Text(0, false, face, ellipsis, false, bold).x
|
||||
end
|
||||
|
||||
--- Returns a substring of a given text that meets the maximum width (in pixels)
|
||||
-- restriction with ellipses (…) at the end if required.
|
||||
--
|
||||
@@ -275,10 +280,36 @@ local ellipsis = "…"
|
||||
-- @treturn string
|
||||
-- @see getSubTextByWidth
|
||||
function RenderText:truncateTextByWidth(text, face, max_width, kerning, bold)
|
||||
local ellipsis_width = self:sizeUtf8Text(0, max_width, face, ellipsis, false, bold).x
|
||||
local ellipsis_width = self:getEllipsisWidth(face, bold)
|
||||
local new_txt_width = max_width - ellipsis_width
|
||||
local sub_txt = self:getSubTextByWidth(text, face, new_txt_width, kerning, bold)
|
||||
return sub_txt .. ellipsis
|
||||
end
|
||||
|
||||
--- Returns a rendered glyph by glyph index
|
||||
-- xtext/Harfbuzz, after shaping, gives glyph indexes in the font, which
|
||||
-- is usually different from the unicode codepoint of the original char)
|
||||
--
|
||||
-- @tparam ui.font.FontFaceObj face font face for the text
|
||||
-- @int glyph index
|
||||
-- @bool[opt=false] bold whether the glyph should be artificially boldened
|
||||
-- @treturn glyph
|
||||
function RenderText:getGlyphByIndex(face, glyphindex, bold)
|
||||
local hash = "xglyph|"..face.hash.."|"..glyphindex.."|"..(bold and 1 or 0)
|
||||
local glyph = GlyphCache:check(hash)
|
||||
if glyph then
|
||||
-- cache hit
|
||||
return glyph[1]
|
||||
end
|
||||
local rendered_glyph = face.ftface:renderGlyphByIndex(glyphindex, bold and face.embolden_half_strength)
|
||||
if not rendered_glyph then
|
||||
logger.warn("error rendering glyph (glyphindex=", glyphindex, ") for face", face)
|
||||
return
|
||||
end
|
||||
glyph = CacheItem:new{rendered_glyph}
|
||||
glyph.size = glyph[1].bb:getWidth() * glyph[1].bb:getHeight() / 2 + 32
|
||||
GlyphCache:insert(hash, glyph)
|
||||
return rendered_glyph
|
||||
end
|
||||
|
||||
return RenderText
|
||||
|
||||
Reference in New Issue
Block a user