2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/frontend/ui/rendertext.lua

184 lines
6.4 KiB
Lua
Raw Normal View History

2013-10-23 14:37:55 +00:00
local Font = require("ui/font")
local Screen = require("device").screen
2013-10-18 20:38:07 +00:00
local Cache = require("cache")
local CacheItem = require("cacheitem")
local BlitBuffer = require("ffi/blitbuffer")
2013-10-22 18:51:29 +00:00
local DEBUG = require("dbg")
2012-05-18 22:35:09 +00:00
--[[
TODO: all these functions should probably be methods on Face objects
]]--
2013-10-18 20:38:07 +00:00
local RenderText = {}
2012-05-18 22:35:09 +00:00
2013-10-18 20:38:07 +00:00
local GlyphCache = Cache:new{
2014-03-13 13:52:43 +00:00
max_memsize = 512*1024,
current_memsize = 0,
cache = {},
-- this will hold the LRU order of the cache
cache_order = {}
}
-- iterator over UTF8 encoded characters in a string
local function utf8Chars(input)
2014-03-13 13:52:43 +00:00
local function read_next_glyph(input, pos)
if string.len(input) < pos then return nil end
local value = string.byte(input, pos)
if bit.band(value, 0x80) == 0 then
-- TODO: check valid ranges
return pos+1, value, string.sub(input, pos, pos)
elseif bit.band(value, 0xC0) == 0x80 -- invalid, continuation
or bit.band(value, 0xF8) == 0xF8 -- 5-or-more byte sequence, illegal due to RFC3629
then
return pos+1, 0xFFFD, "\xFF\xFD"
else
local glyph, bytes_left
if bit.band(value, 0xE0) == 0xC0 then
glyph = bit.band(value, 0x1F)
bytes_left = 1
elseif bit.band(value, 0xF0) == 0xE0 then
glyph = bit.band(value, 0x0F)
bytes_left = 2
elseif bit.band(value, 0xF8) == 0xF0 then
glyph = bit.band(value, 0x07)
bytes_left = 3
else
return pos+1, 0xFFFD, "\xFF\xFD"
end
if string.len(input) < (pos + bytes_left - 1) then
return pos+1, 0xFFFD, "\xFF\xFD"
end
for i = pos+1, pos + bytes_left do
value = string.byte(input, i)
if bit.band(value, 0xC0) == 0x80 then
glyph = bit.bor(bit.lshift(glyph, 6), bit.band(value, 0x3F))
else
return i+1, 0xFFFD, "\xFF\xFD"
end
end
-- TODO: check for valid ranges here!
return pos+bytes_left+1, glyph, string.sub(input, pos, pos+bytes_left)
end
end
return read_next_glyph, input, 1
end
function RenderText:getGlyph(face, charcode, bold)
local hash = "glyph|"..face.hash.."|"..charcode.."|"..(bold and 1 or 0)
2014-03-13 13:52:43 +00:00
local glyph = GlyphCache:check(hash)
if glyph then
-- cache hit
return glyph[1]
end
local rendered_glyph = face.ftface:renderGlyph(charcode, bold)
2014-03-13 13:52:43 +00:00
if face.ftface:checkGlyph(charcode) == 0 then
for index, font in pairs(Font.fallbacks) do
-- use original size before scaling by screen DPI
local fb_face = Font:getFace(font, face.orig_size)
2014-03-13 13:52:43 +00:00
if fb_face.ftface:checkGlyph(charcode) ~= 0 then
rendered_glyph = fb_face.ftface:renderGlyph(charcode, bold)
2014-03-13 13:52:43 +00:00
--DEBUG("fallback to font", font)
break
end
end
end
if not rendered_glyph then
DEBUG("error rendering glyph (charcode=", charcode, ") 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
2014-01-22 09:18:20 +00:00
function RenderText:getSubTextByWidth(text, face, width, kerning, bold)
2014-03-13 13:52:43 +00:00
local pen_x = 0
local prevcharcode = 0
local char_list = {}
for _, charcode, uchar in utf8Chars(text) do
if pen_x < width then
local glyph = self:getGlyph(face, charcode, bold)
if kerning and prevcharcode then
local kern = face.ftface:getKerning(prevcharcode, charcode)
pen_x = pen_x + kern
end
pen_x = pen_x + glyph.ax
if pen_x <= width then
prevcharcode = charcode
table.insert(char_list, uchar)
else
break
end
end
end
return table.concat(char_list)
end
2014-01-22 09:18:20 +00:00
function RenderText:sizeUtf8Text(x, width, face, text, kerning, bold)
2014-03-13 13:52:43 +00:00
if not text then
DEBUG("sizeUtf8Text called without text");
return
end
2014-03-13 13:52:43 +00:00
-- may still need more adaptive pen placement when kerning,
-- see: http://freetype.org/freetype2/docs/glyphs/glyphs-4.html
local pen_x = 0
local pen_y_top = 0
local pen_y_bottom = 0
local prevcharcode = 0
for _, charcode, uchar in utf8Chars(text) do
if 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)
end
pen_x = pen_x + glyph.ax
pen_y_top = math.max(pen_y_top, glyph.t)
pen_y_bottom = math.max(pen_y_bottom, glyph.bb:getHeight() - glyph.t)
--DEBUG("ax:"..glyph.ax.." t:"..glyph.t.." r:"..glyph.r.." h:"..glyph.bb:getHeight().." w:"..glyph.bb:getWidth().." yt:"..pen_y_top.." yb:"..pen_y_bottom)
prevcharcode = charcode
end -- if pen_x < (width - x)
end
return { x = pen_x, y_top = pen_y_top, y_bottom = pen_y_bottom}
2012-03-19 18:41:09 +00:00
end
function RenderText:renderUtf8Text(buffer, x, y, face, text, kerning, bold, fgcolor, width)
2014-03-13 13:52:43 +00:00
if not text then
DEBUG("renderUtf8Text called without text");
return 0
end
if not fgcolor then
fgcolor = BlitBuffer.COLOR_BLACK
end
2014-03-13 13:52:43 +00:00
-- may still need more adaptive pen placement when kerning,
-- see: http://freetype.org/freetype2/docs/glyphs/glyphs-4.html
local pen_x = 0
local prevcharcode = 0
local text_width = buffer:getWidth() - x
if width and width < text_width then
text_width = width
end
for _, charcode, uchar in utf8Chars(text) do
if pen_x < text_width then
local glyph = self:getGlyph(face, charcode, bold)
2014-03-13 13:52:43 +00:00
if kerning and (prevcharcode ~= 0) then
pen_x = pen_x + face.ftface:getKerning(prevcharcode, charcode)
end
buffer:colorblitFrom(
2014-03-13 13:52:43 +00:00
glyph.bb,
x + pen_x + glyph.l, y - glyph.t,
0, 0,
glyph.bb:getWidth(), glyph.bb:getHeight(),
fgcolor)
2014-03-13 13:52:43 +00:00
pen_x = pen_x + glyph.ax
prevcharcode = charcode
end -- if pen_x < text_width
end
2014-03-13 13:52:43 +00:00
return pen_x
end
2013-10-18 20:38:07 +00:00
return RenderText