diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/luaotfload/luaotfload-harf-var-cff2.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaotfload/luaotfload-harf-var-cff2.lua | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-harf-var-cff2.lua b/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-harf-var-cff2.lua new file mode 100644 index 00000000000..9c05b7a6c72 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-harf-var-cff2.lua @@ -0,0 +1,442 @@ +----------------------------------------------------------------------- +-- FILE: luaotfload-harf-var-cff2.lua +-- DESCRIPTION: part of luaotfload / HarfBuzz / Parse and convert CFF2 tables +----------------------------------------------------------------------- +do + assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") { + name = "luaotfload-harf-var-cff2", + version = "3.19", --TAGVERSION + date = "2022-02-24", --TAGDATE + description = "luaotfload submodule / CFF2 table processing", + license = "GPL v2.0", + author = "Marcel Krüger", + copyright = "Luaotfload Development Team", + } +end + +local hb = assert(luaotfload.harfbuzz) +local cff2 = hb.Tag.new'CFF2' +local serialize = require'luaotfload-harf-var-t2-writer' + +local offsetfmt = ">I%i" +local function parse_index(buf, i) + local count, offsize + count, offsize, i = string.unpack(">I4B", buf, i) + if count == 0 then return {}, i-1 end + local fmt = offsetfmt:format(offsize) + local offsets = {} + local dataoffset = i + offsize*count - 1 + for j=1,count+1 do + offsets[j], i = string.unpack(fmt, buf, i) + end + for j=1,count+1 do + offsets[j] = offsets[j] + i - 1 + end + return offsets, offsets[#offsets] +end + +local real_mapping = { [0] = '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', 'E', 'E-', nil, '-', nil} +local function parse_real(cs, offset) + local c = cs:byte(offset) + if not c then return offset end + local c1, c2 = real_mapping[c>>4], real_mapping[c&0xF] + if not c1 or not c2 then + return c1 or offset, c1 and offset + else + return c1, c2, parse_real(cs, offset+1) --Warning: This is not a tail-call, + -- so we are affected by the stack limit. On the other hand, as long as + -- there are less than ~50 bytes we should be safe. + end +end + +local function get_number(result) + if #result ~= 1 then + print(require'inspect'(result)) + end + assert(#result == 1) + local num = result[1] + result[1] = nil + return num +end + +local function get_bool(result) + return get_number(result) == 1 +end + +local function get_array(result) + local arr = table.move(result, 1, #result, 1, {}) + for i=1,#result do result[i] = nil end + return arr +end + +local function get_delta(result) + local arr = get_array(result) + local last = 0 + for i=1,#arr do + arr[i] = arr[i]+last + last = arr[i] + end + return arr +end + +local function get_private(result) + local arr = get_array(result) + assert(#arr == 2) + return arr +end + +local function do_blend(result, vstore) + if not vstore then + error'blend operator only allowed in Private disctionary of variable fonts' + end + local vsindex = (result.vsindex or 0) + 1 + local factors = vstore[vsindex] + local n = result[#result] + local k = #factors + local before = #result - 1 - n*(k+1) + for i = 1, n do + local val = result[before + i] + for j = 1, k do + val = val + factors[j] * result[before + n + (i-1) * k + j] + end + result[before + i] = math.floor(val + .5) + end + for i = before + n + 1, #result do + result[i] = nil + end + return arr +end + +local function apply_matrix(m, x, y) + return (m[1] * x + m[3] * y + m[5])*1000, (m[2] * x + m[4] * y + m[6])*1000 +end + +local operators = { + [6] = {'BlueValues', get_delta}, + [7] = {'OtherBlues', get_delta}, + [8] = {'FamilyBlues', get_delta}, + [9] = {'FamilyOtherBlues', get_delta}, + [10] = {'StdHW', get_number}, + [11] = {'StdVW', get_number}, + [17] = {'CharStrings', get_number}, + [18] = {'Private', get_private}, + [19] = {'Subrs', get_number}, + [22] = {'vsindex', get_number}, + [23] = {'blend', do_blend}, + [24] = {'vstore', get_number}, + [-8] = {'FontMatrix', get_array}, +[-10] = {'BlueScale', get_number}, +[-11] = {'BlueShift', get_number}, +[-12] = {'BlueFuzz', get_number}, +[-13] = {'StemSnapH', get_delta}, +[-14] = {'StemSnapV', get_delta}, +[-15] = {'ForceBold', get_bool}, -- ??? +[-18] = {'LanguageGroup', get_number}, +[-19] = {'ExpansionFactor', get_number}, +[-20] = {'initialRandomSeed', get_number}, -- ??? +[-37] = {'FDArray', get_number}, +[-38] = {'FDSelect', get_number}, +} +local function parse_dict(buf, i, j, vstore) + result = {} + while i<=j do + local cmd = buf:byte(i) + if cmd == 29 then + result[#result+1] = string.unpack(">i4", buf:sub(i+1, i+4)) + i = i+4 + elseif cmd == 28 then + result[#result+1] = string.unpack(">i2", buf:sub(i+1, i+2)) + i = i+2 + elseif cmd >= 251 then -- Actually "and cmd ~= 255", but 255 is reserved + result[#result+1] = -((cmd-251)*256)-string.byte(buf, i+1)-108 + i = i+1 + elseif cmd >= 247 then + result[#result+1] = (cmd-247)*256+string.byte(buf, i+1)+108 + i = i+1 + elseif cmd >= 32 then + result[#result+1] = cmd-139 + elseif cmd == 30 then -- 31 is reserved again + local real = {parse_real(buf, i+1)} + i = real[#real] + real[#real] = nil + result[#result+1] = tonumber(table.concat(real)) + else + if cmd == 12 then + i = i+1 + cmd = -buf:byte(i)-1 + end + local op = operators[cmd] + if not op then error[[Unknown CFF operator]] end + result[op[1]] = op[2](result, vstore) + end + i = i+1 + end + return result +end + +local function parse_charstring(buf, start, after, globalsubrs, subrs, result) + local lastresult = result[#result] + while start ~= after do + local cmd = buf:byte(start) + if cmd == 28 then + lastresult[#lastresult+1] = string.unpack(">i2", buf:sub(start+1, start+2)) + start = start+2 + elseif cmd == 255 then + lastresult[#lastresult+1] = string.unpack(">i4", buf:sub(start+1, start+4))/0x10000 + start = start+4 + elseif cmd >= 251 then + lastresult[#lastresult+1] = -((cmd-251)*256)-string.byte(buf, start+1)-108 + start = start+1 + elseif cmd >= 247 then + lastresult[#lastresult+1] = (cmd-247)*256+string.byte(buf, start+1)+108 + start = start+1 + elseif cmd >= 32 then + lastresult[#lastresult+1] = cmd-139 + elseif cmd == 10 then + local idx = lastresult[#lastresult]+subrs.bias + local sub_start = subrs[idx] + local sub_stop = subrs[idx+1] + lastresult[#lastresult] = nil + parse_charstring(buf, sub_start, sub_stop, globalsubrs, subrs, result) + lastresult = result[#result] + elseif cmd == 29 then + local idx = lastresult[#lastresult]+globalsubrs.bias + local sub_start = globalsubrs[idx] + local sub_stop = globalsubrs[idx+1] + lastresult[#lastresult] = nil + parse_charstring(buf, sub_start, sub_stop, globalsubrs, subrs, result) + lastresult = result[#result] + elseif cmd == 11 then + break -- We do not keep subroutines, so drop returns and continue with the outer commands + elseif cmd == 15 then -- vsindex + assert(#lastresult == 2) + result.factors = result.vstore[lastresult[2] + 1] + lastresult[2] = nil + elseif cmd == 16 then -- blend + local factors = result.factors + if not factors then + error'blend operator outside of variable font or with invalid vsindex' + end + local n = lastresult[#lastresult] + local k = #factors + local before = #lastresult - 1 - n*(k+1) + for i = 1, n do + local val = lastresult[before + i] + for j = 1, k do + val = val + factors[j] * lastresult[before + n + (i-1) * k + j] + end + lastresult[before + i] = math.floor(val + .5) + end + for i = before + n + 1, #lastresult do + lastresult[i] = nil + end + elseif cmd == 12 then + start = start+1 + cmd = buf:byte(start) + lastresult[1] = -cmd-1 + lastresult = {false} + result[#result+1] = lastresult + elseif cmd == 19 or cmd == 20 then + if #result == 1 then + lastresult = {} + result[#result+1] = lastresult + end + lastresult[1] = cmd + local newi = start+(result.stemcount+7)//8 + lastresult[2] = buf:sub(start+1, newi) + start = newi + else + if cmd == 21 and #result == 1 then + table.insert(result, 1, {false}) + if #lastresult == 4 then + result[1][2] = lastresult[2] + table.remove(lastresult, 2) + end + elseif (cmd == 4 or cmd == 22) and #result == 1 then + table.insert(result, 1, {false}) + if #lastresult == 3 then + result[1][2] = lastresult[2] + table.remove(lastresult, 2) + end + elseif cmd == 14 and #result == 1 then + table.insert(result, 1, {false}) + if #lastresult == 2 or #lastresult == 6 then + result[1][2] = lastresult[2] + table.remove(lastresult, 2) + end + elseif cmd == 1 or cmd == 3 or cmd == 18 or cmd == 23 then + if #result == 1 then + table.insert(result, 1, {false}) + if #lastresult % 2 == 0 then + result[1][2] = lastresult[2] + table.remove(lastresult, 2) + end + end + result.stemcount = result.stemcount + #lastresult//2 + end + lastresult[1] = cmd + lastresult = {false} + result[#result+1] = lastresult + end + start = start+1 + end + return result +end + +local function parse_fdselect(buf, offset, CharStrings) + local format + format, offset = string.unpack(">B", buf, offset) + if format == 0 then + for i=0,#CharStrings-1 do + local code + code, offset = string.unpack(">B", buf, offset) + CharStrings[i][3] = code + 1 + end -- Reimplement with string.byte + elseif format == 3 then + local count, last + count, offset = string.unpack(">I2", buf, offset) + for i=1,count do + local first, code, after = string.unpack(">I2BI2", buf, offset) + for j=first, after-1 do + CharStrings[j][3] = code + 1 + end + offset = offset + 3 + end + elseif format == 4 then + local count, last + count, offset = string.unpack(">I4", buf, offset) + for i=1,count do + local first, code, after = string.unpack(">I4I2I4", buf, offset) + for j=first, after-1 do + CharStrings[j][3] = code + 1 + end + offset = offset + 6 + end + else + error[[Invalid FDSelect format]] + end +end + +local function parse_vstore(buf, offset, variation) + local size, format, region_list_off, item_variation_count, off = string.unpack(">I2I2I4I2", buf, offset) + if format ~= 1 then + error'Unsupported vstore format' + end + offset = offset + 2 -- Skip the size + region_list_off = offset + region_list_off + + local axis_count, region_count + axis_count, region_count, region_list_off = string.unpack(">I2I2", buf, region_list_off) + + local variation_regions = {} + for i = 1, region_count do + local factor = 1 + for j = 1, axis_count do + local start, peak, stop + start, peak, stop, region_list_off = string.unpack(">i2i2i2", buf, region_list_off) + local coord = variation[j] + if peak == 0 then -- Skip + elseif peak == coord then + -- factor = factor * 1 + elseif coord <= start or coord >= stop then + factor = 0 + break + elseif coord < peak then + factor = factor * ((coord-start) / (peak-start)) + else--if coord > peak then + factor = factor * ((stop-coord) / (stop-peak)) + end + end + variation_regions[i] = factor + end + + local variation_data = {} + for i = 1, item_variation_count do + local item_off + item_off, off = string.unpack(">I4", buf, off) + local i_count, short_count, region_count + i_count, short_count, region_count, item_off = string.unpack(">I2I2I2", buf, item_off + offset) + if i_count ~= 0 or short_count ~= 0 then + error'Unexpected variation items in CFF2 table' + end + local factors = {} + for j = 1, region_count do + local region + region, item_off = string.unpack(">I2", buf, item_off) + factors[j] = variation_regions[region+1] + end + variation_data[i] = factors + end + return variation_data +end + +function parse_cff2(buf, i0, coords) + local fontid = 1 + local major, minor, hdrSize, topSize = string.unpack(">BBBH", buf, i0) + if major ~= 2 then error[[Unsupported CFF version]] end + local i = i0 + hdrSize + local top = parse_dict(buf, i, i + topSize - 1) + i = i + topSize + local globalsubrs + globalsubrs, i = parse_index(buf, i) + globalsubrs.bias = #globalsubrs-1 < 1240 and 108 or #globalsubrs-1 < 33900 and 1132 or 32769 + top.GlobalSubrs = globalsubrs + local CharStrings = parse_index(buf, i0+top.CharStrings) + for i=1,#CharStrings-1 do + CharStrings[i-1] = {CharStrings[i], CharStrings[i+1]-1} + end + CharStrings[#CharStrings] = nil + CharStrings[#CharStrings] = nil + local fonts = parse_index(buf, i0+top.FDArray) + top.FDArray = nil + top.vstore = parse_vstore(buf, i0 + top.vstore, coords) + local privates = {} + top.Privates = privates + for i=1,#fonts-1 do + local font = fonts[i] + local fontdir = parse_dict(buf, fonts[i], fonts[i+1]-1) + privates[i] = parse_dict(buf, i0+fontdir.Private[2], i0+fontdir.Private[2]+fontdir.Private[1]-1, top.vstore) + local subrs = privates[i].Subrs + if subrs then + subrs = parse_index(buf, i0+fontdir.Private[2]+subrs) + subrs.bias = #subrs-1 < 1240 and 108 or #subrs-1 < 33900 and 1132 or 32769 + privates[i].Subrs = subrs + end + end + if top.FDSelect then + parse_fdselect(buf, i0+top.FDSelect, CharStrings) + else + for i=0,#CharStrings-1 do + CharStrings[i][3] = 1 + end + end + top.CharStrings = CharStrings + local bbox + if top.FontMatrix then + local x0, y0 = apply_matrix(top.FontMatrix, top.FontBBox[1], top.FontBBox[2]) + local x1, y1 = apply_matrix(top.FontMatrix, top.FontBBox[3], top.FontBBox[4]) + bbox = {x0, y0, x1, y1} + else + bbox = top.FontBBox + end + return top, bbox +end + +local function parse_glyph(buffer, top, gid) + local cs = top.CharStrings[gid] + local Private = top.Privates[cs[3]] + return parse_charstring(buffer, cs[1], cs[2] + 1, + top.GlobalSubrs, Private.Subrs, + {{false}, stemcount = 0, vstore = top.vstore, factors = top.vstore and top.vstore[(Private.vsindex or 0) + 1]}) +end + +return function(face, font) + local data = face:get_table(cff2):get_data() + local content = parse_cff2(data, 1, {font:get_var_coords_normalized()}) + return function(gid) + local glyph = parse_glyph(data, content, gid) + glyph[1][2] = font:get_glyph_h_advance(gid) + return serialize(glyph) + end +end |