[latex3-commits] [git/LaTeX3-latex3-luaotfload] harfnode-dev: Fixes and rewrite of COLR glyphs (90f63ed)
Marcel Fabian Krüger
tex at 2krueger.de
Sat Oct 12 03:59:50 CEST 2019
Repository : https://github.com/latex3/luaotfload
On branch : harfnode-dev
Link : https://github.com/latex3/luaotfload/commit/90f63edd96a3e163187ba7ded25fb794074442b7
>---------------------------------------------------------------
commit 90f63edd96a3e163187ba7ded25fb794074442b7
Author: Marcel Fabian Krüger <tex at 2krueger.de>
Date: Sat Oct 12 03:59:50 2019 +0200
Fixes and rewrite of COLR glyphs
>---------------------------------------------------------------
90f63edd96a3e163187ba7ded25fb794074442b7
src/luaotfload-harf-plug.lua | 313 +++++++++++++++++++++++--------------------
1 file changed, 165 insertions(+), 148 deletions(-)
diff --git a/src/luaotfload-harf-plug.lua b/src/luaotfload-harf-plug.lua
index 35cd523..1425aef 100644
--- a/src/luaotfload-harf-plug.lua
+++ b/src/luaotfload-harf-plug.lua
@@ -59,6 +59,8 @@ local setwidth = direct.setwidth
local is_char = direct.is_char
local tail = direct.tail
+local properties = direct.get_properties_table()
+
local imgnode = img.node
local disc_t = node.id("disc")
@@ -86,8 +88,7 @@ local fl_unsafe = hb.Buffer.GLYPH_FLAG_UNSAFE_TO_BREAK
local startactual_p = "startactualtext"
local endactual_p = "endactualtext"
-local color_p = "color"
-local string_p = "string"
+local color_p = "color" -- FIXME: Colors are better handled as attributes
-- Simple table copying function.
local function copytable(old)
@@ -299,7 +300,7 @@ local function printnodes(label, head)
end
-- Main shaping function that calls HarfBuzz, and does some post-processing of
-- the output.
-shape = function(head, node, run)
+function shape(head, node, run)
local codes = run.codes
local offset = run.start - (codes.offset or 0)
run.start = offset
@@ -311,13 +312,11 @@ shape = function(head, node, run)
local fontdata = font.getfont(fontid)
local hbdata = fontdata.hb
- local palette = hbdata.palette
local spec = hbdata.spec
local features = spec.hb_features
local options = spec.features.raw
local hbshared = hbdata.shared
local hbfont = hbshared.font
- local hbface = hbshared.face
local lang = options.language or invalid_l
local script = options.script or invalid_s
@@ -341,44 +340,6 @@ shape = function(head, node, run)
local glyphs = buf:get_glyphs()
- -- If the font has COLR/CPAL tables, decompose each glyph to its color
- -- layers and set the color from the palette.
- if palette then
- for i, glyph in next, glyphs do
- local gid = glyph.codepoint
- local layers = hbface:ot_color_glyph_get_layers(gid)
- if layers then
- -- Remove this glyph, we will use its layers.
- tableremove(glyphs, i)
- for j, layer in next, layers do
- -- All glyphs but the last use 0 advance so that the layers
- -- overlap.
- local xadavance, yadvance = nil, nil
- if dir:is_backward() then
- x_advance = j == 1 and glyph.x_advance or 0
- y_advance = j == 1 and glyph.y_advance or 0
- else
- x_advance = j == #layers and glyph.x_advance or 0
- y_advance = j == #layers and glyph.y_advance or 0
- end
- tableinsert(glyphs, i + j - 1, {
- codepoint = layer.glyph,
- cluster = glyph.cluster,
- x_advance = x_advance,
- y_advance = y_advance,
- x_offset = glyph.x_offset,
- y_offset = glyph.y_offset,
- flags = glyph.flags,
- -- color_index has a special value, 0x10000, that mean use text
- -- color, we don’t check for it here explicitly since we will
- -- get nil anyway.
- color = palette[layer.color_index],
- })
- end
- end
- end
- end
-
local i = 0
while i < #glyphs do
i = i + 1
@@ -580,113 +541,160 @@ end
-- Cache of color glyph PNG data for bookkeeping, only because I couldn’t
-- figure how to make the engine load the image from the binary data directly.
local pngcache = {}
+local pngcachefiles = {}
local function cachedpng(data)
local hash = md5.sumhexa(data)
- local path = pngcache[hash]
- if not path then
- path = ostmpname()
- local file = open(path, "wb")
- file:write(data)
- file:close()
- pngcache[hash] = path
- end
- return path
+ local i = pngcache[hash]
+ if not i then
+ local path = ostmpname()
+ pngcachefiles[#pngcachefiles + 1] = path
+ open(path, "wb"):write(data):close()
+ -- local file = open(path, "wb"):write():close()
+ -- file:write(data)
+ -- file:close()
+ i = img.scan{filename = path}
+ pngcache[hash] = i
+end
+return i
end
local function get_png_glyph(gid, fontid, characters, haspng)
- return gid
+return gid
end
+local push_cmd = { "push" }
+local pop_cmd = { "pop" }
+local nop_cmd = { "nop" }
+local save_cmd = { "pdf", "text", "q" }
+local restore_cmd = { "pdf", "text", "Q" }
+
-- Convert glyphs to nodes and collect font characters.
local function tonodes(head, node, run, glyphs, color)
- local nodeindex = run.start
- local dir = run.dir
- local fontid = run.font
- local fontdata = font.getfont(fontid)
- local characters = fontdata.characters
- local hbdata = fontdata.hb
- local hbshared = hbdata.shared
- local nominals = hbshared.nominals
- local hbfont = hbshared.font
- local fontglyphs = hbshared.glyphs
- local gid_offset = hbshared.gid_offset
- local rtl = dir:is_backward()
- local lastprops
-
- local scale = hbdata.scale
- local letterspace = hbdata.letterspace
-
- local haspng = hbshared.haspng
- local fonttype = hbshared.fonttype
-
- for i, glyph in ipairs(glyphs) do
- if glyph.cluster < nodeindex - 1 then -- Ups, we went too far
- nodeindex = nodeindex - 1
- local new = inherit(glyph_t, getprev(node), lastprops)
- setfont(new, fontid)
- head, node = insertbefore(head, node, new)
- else
- for j = nodeindex, glyph.cluster do
- local oldnode = node
- head, node = removenode(head, node)
- freenode(oldnode)
- end
- lastprops = getproperty(node)
- nodeindex = glyph.cluster + 1
- end
- local gid = glyph.codepoint
- local char = nominals[gid] or gid_offset + gid
- local id = getid(node)
- local nchars, nglyphs = glyph.nchars, glyph.nglyphs
-
- if color then
- setprop(node, color_p, color)
- end
-
- if glyph.replace then
- -- For discretionary the glyph itself is skipped and a discretionary node
- -- is output in place of it.
- local rep, pre, post = glyph.replace, glyph.pre, glyph.post
-
- setdisc(node, tonodes(pre.head, pre.head, pre.run, pre.glyphs, color),
- tonodes(post.head, post.head, post.run, post.glyphs, color),
- tonodes(rep.head, rep.head, rep.run, rep.glyphs, color))
- node = getnext(node)
- nodeindex = nodeindex + 1
- elseif glyph.skip then
+local nodeindex = run.start
+-- local nodeindex = run.start - (run.codes.offset or 0)
+local dir = run.dir
+local fontid = run.font
+local fontdata = font.getfont(fontid)
+local characters = fontdata.characters
+local hbdata = fontdata.hb
+local palette = hbdata.palette
+local hbshared = hbdata.shared
+local hbface = hbshared.face
+local nominals = hbshared.nominals
+local hbfont = hbshared.font
+local fontglyphs = hbshared.glyphs
+local gid_offset = hbshared.gid_offset
+local rtl = dir:is_backward()
+local lastprops
+
+local scale = hbdata.scale
+local letterspace = hbdata.letterspace
+
+local haspng = hbshared.haspng
+local fonttype = hbshared.fonttype
+
+for i, glyph in ipairs(glyphs) do
+ if glyph.cluster < nodeindex - 1 then -- Ups, we went too far
+ nodeindex = nodeindex - 1
+ local new = inherit(glyph_t, getprev(node), lastprops)
+ setfont(new, fontid)
+ head, node = insertbefore(head, node, new)
+ else
+ for j = nodeindex, glyph.cluster do
local oldnode = node
head, node = removenode(head, node)
freenode(oldnode)
- nodeindex = nodeindex + 1
- else
- if glyph.color then
- setprop(node, color_p, color_to_rgba(glyph.color))
- end
+ end
+ lastprops = getproperty(node)
+ nodeindex = glyph.cluster + 1
+ end
+ local gid = glyph.codepoint
+ local char = nominals[gid] or gid_offset + gid
+ local id = getid(node)
+ local nchars, nglyphs = glyph.nchars, glyph.nglyphs
+
+ if color then
+ setprop(node, color_p, color)
+ end
+
+ if glyph.replace then
+ -- For discretionary the glyph itself is skipped and a discretionary node
+ -- is output in place of it.
+ local rep, pre, post = glyph.replace, glyph.pre, glyph.post
+
+ setdisc(node, tonodes(pre.head, pre.head, pre.run, pre.glyphs, color),
+ tonodes(post.head, post.head, post.run, post.glyphs, color),
+ tonodes(rep.head, rep.head, rep.run, rep.glyphs, color))
+ node = getnext(node)
+ nodeindex = nodeindex + 1
+ elseif glyph.skip then
+ local oldnode = node
+ head, node = removenode(head, node)
+ freenode(oldnode)
+ nodeindex = nodeindex + 1
+ else
+ if glyph.color then
+ setprop(node, color_p, color_to_rgba(glyph.color)) -- FIXME: Precompute this
+ end
- if id == glyph_t then
- local done
- local fontglyph = fontglyphs[gid]
- local character = characters[char]
+ if id == glyph_t then
+ local done
+ local fontglyph = fontglyphs[gid]
+ local character = characters[char]
+
+ if not character.commands then
+ if palette then
+ local layers = fontglyph.layers
+ if layers == nil then
+ layers = hbface:ot_color_glyph_get_layers(gid)
+ if layers then
+ local cmds = {} -- Every layer will add 3 cmds
+ local prev_color = nil
+ for j = 1, #layers do
+ local layer = layers[j]
+ local layerchar = characters[gid_offset + layer.glyph]
+ if layerchar.height > character.height then
+ character.height = layerchar.height
+ end
+ if layerchar.depth > character.depth then
+ character.depth = layerchar.depth
+ end
+ -- color_index has a special value, 0x10000, that mean use text
+ -- color, we don’t check for it here explicitly since we will
+ -- get nil anyway.
+ local color = palette[layer.color_index]
+ cmds[5*j - 4] = (color and not prev_color) and save_cmd or nop_cmd
+ cmds[5*j - 3] = prev_color == color and nop_cmd or (color and {"pdf", "text", color_to_rgba(color)} or restore_cmd)
+ cmds[5*j - 2] = push_cmd
+ cmds[5*j - 1] = {"char", layer.glyph + gid_offset}
+ cmds[5*j] = pop_cmd
+ fontglyphs[layer.glyph].used = true
+ prev_color = color
+ end
+ cmds[#cmds + 1] = prev_color and restore_cmd
+ layers = cmds
+ else
+ layers = false
+ end
+ fontglyph.layers = layers
+ end
+ if layers then
+ character.commands = layers
+ font.addcharacters(fontid, {characters = {[gid + gid_offset] = character, [char] = character}})
+ end
+ end
if haspng then
local pngglyph = character.pngglyph
if pngglyph == nil then
- pngglyph = fontglyph.png
- if pngglyph == nil then
- local pngblob = hbfont:ot_color_glyph_get_png(gid)
- if pngblob then
- pngglyph = img.scan{filename = cachedpng(pngblob:get_data())}
- else
- pngglyph = false
- end
- fontglyph.png = pngglyph
- end
- if pngglyph then
+ local pngblob = hbfont:ot_color_glyph_get_png(gid)
+ if pngblob then
+ local glyphimg = cachedpng(pngblob:get_data())
local pngchar = { }
for k,v in next, character do
pngchar[k] = v
end
- local i = img.copy(pngglyph)
+ local i = img.copy(glyphimg)
i.width = character.width
i.depth = 0
i.height = character.height + character.depth
@@ -703,19 +711,23 @@ local function tonodes(head, node, run, glyphs, color)
end
character.pngglyph = pngglyph
end
- if pngglyph then
- char = pngglyph
- elseif not fonttype then
- -- Color bitmap font with no glyph outlines (like Noto
- -- Color Emoji) but has no bitmap for current glyph (most likely
- -- `.notdef` glyph). The engine does not know how to embed such
- -- fonts, so we don’t want them to reach the backend as it will cause
- -- a fatal error. We use `nullfont` instead. That is a hack, but I
- -- think it is good enough for now.
- -- We insert the glyph node and move on, no further work is needed.
- setfont(node, 0)
- done = true
+ if pngglyph then
+ char = pngglyph
+ elseif not fonttype then
+ -- Color bitmap font with no glyph outlines (like Noto
+ -- Color Emoji) but has no bitmap for current glyph (most likely
+ -- `.notdef` glyph). The engine does not know how to embed such
+ -- fonts, so we don’t want them to reach the backend as it will cause
+ -- a fatal error. We use `nullfont` instead. That is a hack, but I
+ -- think it is good enough for now. We could make the glyph virtual
+ -- with empty commands suh that LuaTeX ignores it, but we still want
+ -- a missing glyph warning.
+ -- We insert the glyph node and move on, no further work is needed.
+ setfont(node, 0)
+ done = true
+ end
end
+ ;
end
if not done then
local oldcharacter = characters[getchar(node)]
@@ -738,7 +750,12 @@ local function tonodes(head, node, run, glyphs, color)
-- If the string is empty it means this glyph is part of a larger
-- cluster and we don’t to print anything for it as the first glyph
-- in the cluster will have the string of the whole cluster.
- setprop(node, string_p, glyph.string or "")
+ local props = properties[node]
+ if not props then
+ props = {}
+ properties[node] = props
+ end
+ props.glyph_info = glyph.string or ""
-- Handle PDF text extraction:
-- * Find how many characters in this cluster and how many glyphs,
@@ -849,7 +866,7 @@ local function shape_run(head, current, run)
local fontid = run.font
local fontdata = font.getfont(fontid)
local options = fontdata.specification.features.raw
- local color = options and options.color and hex_to_rgba(options.color)
+ local color = options and options.color and hex_to_rgba(options.color) -- FIXME: Precompute this
local glyphs
head, glyphs = shape(head, current, run)
@@ -938,7 +955,7 @@ end
local function run_cleanup()
-- Remove temporary PNG files that we created, if any.
-- FIXME: It would be nice if we wouldn't need this
- for _, path in next, pngcache do
+ for _, path in next, pngcachefiles do
osremove(path)
end
end
@@ -971,11 +988,11 @@ local function set_tounicode()
end
end
+-- FIXME: Move this into generic parts of luaotfload
local function get_glyph_info(n)
- local n = todirect(n)
- local props = getproperty(n)
- props = props and props.harf
- return props and props[string_p] or nil
+ local props = properties[todirect(n)]
+ if not props then return end
+ return props and props.glyph_info or nil
end
fonts.handlers.otf.registerplugin('harf', process)
More information about the latex3-commits
mailing list