diff options
author | Karl Berry <karl@freefriends.org> | 2021-03-06 21:58:15 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-03-06 21:58:15 +0000 |
commit | 974640d66e61e81cb197ad96fdff7b08343e4c5a (patch) | |
tree | 2e1f75f32f312b7f24ba82b4590ae230bcd6f399 /Master/texmf-dist/tex/context/base/mkiv/font-ocm.lua | |
parent | b4fa72e61230aca75f7f6fbf988821f71edfb6b2 (diff) |
context
git-svn-id: svn://tug.org/texlive/trunk@58167 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/font-ocm.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/font-ocm.lua | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/font-ocm.lua b/Master/texmf-dist/tex/context/base/mkiv/font-ocm.lua new file mode 100644 index 00000000000..2f6c29e65d8 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkiv/font-ocm.lua @@ -0,0 +1,874 @@ +if not modules then modules = { } end modules ['font-ocm'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +if not context then + return +elseif CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then + return +else + -- Maybe I'll also make a generic variant but for now I just test this in + -- MkIV. After all, color fonts are not that much used (and generic is for + -- serious looking articles and books and not for fancy documents using + -- emoji.) Below is a quick and dirty implementation. Also, it looks like + -- these features were never used outside context anyway (in spite of being + -- in generic). +end + +local tostring, tonumber, next = tostring, tonumber, next +local round, max = math.round, math.round +local sortedkeys, sortedhash, concat = table.sortedkeys, table.sortedhash, table.concat +local setmetatableindex = table.setmetatableindex +local formatters = string.formatters + +local otf = fonts.handlers.otf +local otfregister = otf.features.register +local bpfactor = number.dimenfactors.bp +local typethree = { } + +callback.register("provide_charproc_data",function(action,f,...) + local registered = typethree[f] + if registered then + return registered(action,f,...) + else + return 0, 0 -- this will also disable further calls + end +end) + +local defaults = { + [1] = function() return 0, 0 end, + [2] = function() return 0, 0 end, + [3] = function() return 0.001, "" end, +} + +local function registeractions(t) + return { + [1] = t.preroll or defaults[1], + [2] = t.collect or defaults[2], + [3] = t.wrapup or defaults[3], + } +end + +local function registertypethreeresource(specification,n,o) + specification.usedobjects["X"..n] = lpdf.reference(o) +end + +local function registertypethreefont(specification,n,o) + specification.usedfonts["F"..n] = lpdf.reference(o) +end + +local function typethreeresources(specification) + local usedobjects = specification.usedobjects + local usedfonts = specification.usedfonts + local resources = { } + if next(usedobjects) then + resources[#resources+1] = "/XObject << " .. usedobjects() .. " >>" + end + if next(usedfonts) then + resources[#resources+1] = "/Font << " .. usedfonts() .. " >>" + end + -- resources[#resources+1] = lpdf.collectedresources() + specification.usedfonts = nil + specification.usedobjects = nil + return concat(resources, " ") +end + +local function registerfont(specification,actions) + specification.usedfonts = lpdf.dictionary() + specification.usedobjects = lpdf.dictionary() + typethree[specification.id] = function(action,f,c) + return actions[action](specification,f,c) + end +end + +fonts.handlers.typethree = { + register = function(id,handler) + -- needed for manual + if not typethree[id] then + logs.report("fonts","low level Type3 handler registered for font with id %i",id) + typethree[id] = handler + end + end +} + +local initializeoverlay do + + local f_color = formatters["%.3f %.3f %.3f rg"] + local f_gray = formatters["%.3f g"] + local sharedpalettes = { } + local colors = attributes.list[attributes.private('color')] or { } + local transparencies = attributes.list[attributes.private('transparency')] or { } + + function otf.registerpalette(name,values) + sharedpalettes[name] = values + local color = lpdf.color + local transparency = lpdf.transparency + local register = colors.register + for i=1,#values do + local v = values[i] + if v == "textcolor" then + values[i] = false + else + local c = nil + local t = nil + if type(v) == "table" then + c = register(name,"rgb", + max(round((v.r or 0)*255),255)/255, + max(round((v.g or 0)*255),255)/255, + max(round((v.b or 0)*255),255)/255 + ) + else + c = colors[v] + t = transparencies[v] + end + if c and t then + values[i] = color(1,c) .. " " .. transparency(t) + elseif c then + values[i] = color(1,c) + elseif t then + values[i] = color(1,t) + end + end + end + end + + local function convert(t,k) + local v = { } + for i=1,#k do + local p = k[i] + local r, g, b = p[1], p[2], p[3] + if r == g and g == b then + v[i] = f_gray(r/255) + else + v[i] = f_color(r/255,g/255,b/255) + end + end + t[k] = v + return v + end + + -- This is by no means watertight (the id mess) especially because we + -- don't know it yet. Instead we can just assemble here and avoid the + -- box approach. I might do that (so then we need to pass fonts and + -- extra resource entries. + + local f_stream = formatters["%s 0 d0 %s 0 0 %s 0 %s cm /X%i Do"] + local fontorder = 0 + local actions = registeractions { + + preroll = function(specification,f,c) + local data = specification.delegated[c] + local colorlist = data.colorlist + local colorvalues = specification.colorvalues + local default = specification.default + local mainid = specification.mainid + local t = { "\\typethreefont{", mainid, "}" } + local n = 3 + local l = nil + local m = #colorlist + for i=1,m do + local entry = colorlist[i] + local v = colorvalues[entry.class] or default + if v and l ~= v then + n = n + 1 ; t[n] = "\\typethreecode{" + n = n + 1 ; t[n] = v + n = n + 1 ; t[n] = "}" + l = v + end + if i < m then + n = n + 1 ; t[n] = "\\typethreechar{" + else + n = n + 1 ; t[n] = "\\typethreelast{" + end + n = n + 1 ; t[n] = entry.slot + n = n + 1 ; t[n] = "}" + end + token.set_macro("typethreemacro",concat(t)) + tex.runlocal("typethreetoks") + registertypethreeresource(specification,c,tex.saveboxresource(0,nil,lpdf.collectedresources(),true)) + -- registertypethreefont(specification,mainid,lpdf.reference(lpdf.getfontobjnumber(mainid))) + return 0, 0 + end, + + collect = function(specification,f,c) + local parameters = specification.parameters + local data = specification.delegated[c] + local factor = parameters.hfactor + local units = parameters.units + local width = (data.width or 0) / factor + local scale = 100 + local factor = units * bpfactor -- / scale + local depth = (data.depth or 0)*factor + local shift = - depth / (10*units/1000) + local object = pdf.immediateobj("stream",f_stream(width,scale,scale,shift,c)) + return object, width + end, + + wrapup = function(specification,f) + return 0.001, typethreeresources(specification) + end, + + } + + local function register(specification) + registerfont(specification,actions) + end + + initializeoverlay = function(tfmdata,kind,value) + if value then + local resources = tfmdata.resources + local palettes = resources.colorpalettes + if palettes then + local converted = resources.converted + if not converted then + converted = setmetatableindex(convert) + resources.converted = converted + end + local colorvalues = sharedpalettes[value] + local default = false -- so the text color (bad for icon overloads) + if colorvalues then + default = colorvalues[#colorvalues] + else + colorvalues = converted[palettes[tonumber(value) or 1] or palettes[1]] or { } + end + local classes = #colorvalues + if classes == 0 then + return + end + -- + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local properties = tfmdata.properties + local parameters = tfmdata.parameters + -- + properties.virtualized = true + -- + local delegated = { } + local index = 0 + local fonts = tfmdata.fonts or { } + local fontindex = #fonts + 1 + tfmdata.fonts = fonts + + local function flush() + if index > 0 then + fontorder = fontorder + 1 + local f = { + characters = delegated, + parameters = parameters, + tounicode = true, + format = "type3", + name = "InternalTypeThreeFont" , -- .. fontorder, + psname = "none", + } + fonts[fontindex] = { + id = font.define(f), + delegated = delegated, + parameters = parameters, + colorvalues = colorvalues, + default = default, + } + end + fontindex = fontindex + 1 + index = 0 + delegated = { } + end + + for unicode, character in sortedhash(characters) do + local description = descriptions[unicode] + if description then + local colorlist = description.colors + if colorlist then + if index == 255 then + flush() + end + index = index + 1 + delegated[index] = { + width = character.width, + height = character.height, + depth = character.depth, + tounicode = character.tounicode, + colorlist = colorlist, + } + character.commands = { + { "slot", fontindex, index }, + } + end + end + end + + flush() + local mainid = font.nextid() + for i=1,#fonts do + local f = fonts[i] + if f.delegated then + f.mainid = mainid + register(f) + end + end + + return true + end + end + end + + otfregister { + name = "colr", + description = "color glyphs", + manipulators = { + base = initializeoverlay, + node = initializeoverlay, + } + } + +end + +do + + local nofstreams = 0 + local f_name = formatters[ [[pdf-glyph-%05i]] ] + local f_used = context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ] + local hashed = { } + local cache = { } + + local openpdf = pdfe.new + + function otf.storepdfdata(pdf) + if pdf then + local done = hashed[pdf] + if not done then + nofstreams = nofstreams + 1 + local f = f_name(nofstreams) + local n = openpdf(pdf,#pdf,f) + done = f_used(n) + hashed[pdf] = done + end + return done + end + end + +end + +local pdftovirtual do + + local f_stream = formatters["%s 0 d0 %s 0 0 %s %s %s cm /X%i Do"] + local fontorder = 0 + local shared = { } + local actions = registeractions { + + preroll = function(specification,f,c) + return 0, 0 + end, + + collect = function(specification,f,c) + local parameters = specification.parameters + local data = specification.delegated[c] + local desdata = data.desdata + local pdfdata = data.pdfdata + local width = desdata.width or 0 + local height = desdata.height or 0 + local depth = desdata.depth or 0 + local factor = parameters.hfactor + local units = parameters.units + local typ = type(pdfdata) + + local dx = 0 + local dy = 0 + local scale = 1 + + if typ == "table" then + data = pdfdata.data + dx = pdfdata.x or pdfdata.dx or 0 + dy = pdfdata.y or pdfdata.dy or 0 + scale = pdfdata.scale or 1 + elseif typ == "string" then + data = pdfdata + dx = 0 + dy = 0 + else + return 0, 0 + end + + if not data then + return 0, 0 + end + + local name = otf.storepdfdata(data) + local xform = shared[name] + + if not xform then + xform = images.embed(images.create { filename = name }) + shared[name] = xform + end + + registertypethreeresource(specification,c,xform.objnum) + + scale = scale * (width / (xform.width * bpfactor)) + dy = - depth + dy +-- dx = 0 +-- dy = 0 + local object = pdf.immediateobj("stream",f_stream(width,scale,scale,dx,dy,c)), width + + return object, width + end, + + wrapup = function(specification,f) + return 1/specification.parameters.units, typethreeresources(specification) + end, + + } + + local function register(specification) + registerfont(specification,actions) + end + + pdftovirtual = function(tfmdata,pdfshapes,kind) -- kind = png|svg + if not tfmdata or not pdfshapes or not kind then + return + end + -- + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local properties = tfmdata.properties + local parameters = tfmdata.parameters + local hfactor = parameters.hfactor + -- + properties.virtualized = true + -- + local storepdfdata = otf.storepdfdata + -- + local delegated = { } + local index = 0 + local fonts = tfmdata.fonts or { } + local fontindex = #fonts + 1 + tfmdata.fonts = fonts + + local function flush() + if index > 0 then + fontorder = fontorder + 1 + local f = { + characters = delegated, + parameters = parameters, + tounicode = true, + format = "type3", + name = "InternalTypeThreeFont" .. fontorder, + psname = "none", + size = parameters.size, + } + fonts[fontindex] = { + id = font.define(f), + delegated = delegated, + parameters = parameters, + } + end + fontindex = fontindex + 1 + index = 0 + delegated = { } + end + + for unicode, character in sortedhash(characters) do + local idx = character.index + if idx then + local pdfdata = pdfshapes[idx] + local description = descriptions[unicode] + if pdfdata and description then + if index == 255 then + flush() + end + index = index + 1 + delegated[index] = { + desdata = description, + width = character.width, + height = character.width, + depth = character.width, + tounicode = character.tounicode, + pdfdata = pdfdata, + } + character.commands = { + { "slot", fontindex, index }, + } + end + end + end + -- + flush() + local mainid = font.nextid() + for i=1,#fonts do + local f = fonts[i] + if f.delegated then + f.mainid = mainid + register(f) + end + end + -- + end + +end + +local initializesvg do + + local otfsvg = otf.svg or { } + otf.svg = otfsvg + otf.svgenabled = true + + local report_svg = logs.reporter("fonts","svg conversion") + + local loaddata = io.loaddata + local savedata = io.savedata + local remove = os.remove + + local xmlconvert = xml.convert + local xmlfirst = xml.first + + function otfsvg.filterglyph(entry,index) + local d = entry.data + if gzip.compressed(d) then + d = gzip.decompress(d) or d + end + local svg = xmlconvert(d) + local root = svg and xmlfirst(svg,"/svg[@id='glyph"..index.."']") + local data = root and tostring(root) + return data + end + + local runner = sandbox and sandbox.registerrunner { + name = "otfsvg", + program = "inkscape", + method = "pipeto", + template = "--export-area-drawing --shell > temp-otf-svg-shape.log", + reporter = report_svg, + } + + if not runner then + -- + -- poor mans variant for generic: + -- + runner = function() + return io.popen("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w") + end + end + + -- There are svg out there with bad viewBox specifications where shapes lay outside that area, + -- but trying to correct that didn't work out well enough so I discarded that code. BTW, we + -- decouple the inskape run and the loading run because inkscape is working in the background + -- in the files so we need to have unique files. + -- + -- Because a generic setup can be flawed we need to catch bad inkscape runs which add a bit of + -- ugly overhead. Bah. + + local new = nil + + local function inkscapeformat(suffix) + if new == nil then + new = os.resultof("inkscape --version") or "" + new = new == "" or not find(new,"Inkscape%s*0") + end + return new and "filename" or suffix + end + + function otfsvg.topdf(svgshapes,tfmdata) + local pdfshapes = { } + local inkscape = runner() + if inkscape then + -- local indices = fonts.getindices(tfmdata) + local descriptions = tfmdata.descriptions + local nofshapes = #svgshapes + local f_svgfile = formatters["temp-otf-svg-shape-%i.svg"] + local f_pdffile = formatters["temp-otf-svg-shape-%i.pdf"] + local f_convert = formatters["%s --export-%s=%s\n"] + local filterglyph = otfsvg.filterglyph + local nofdone = 0 + local processed = { } + report_svg("processing %i svg containers",nofshapes) + statistics.starttiming() + for i=1,nofshapes do + local entry = svgshapes[i] + for index=entry.first,entry.last do + local data = filterglyph(entry,index) + if data and data ~= "" then + local svgfile = f_svgfile(index) + local pdffile = f_pdffile(index) + savedata(svgfile,data) + inkscape:write(f_convert(svgfile,inkscapeformat("pdf"),pdffile)) + processed[index] = true + nofdone = nofdone + 1 + if nofdone % 25 == 0 then + report_svg("%i shapes submitted",nofdone) + end + end + end + end + if nofdone % 25 ~= 0 then + report_svg("%i shapes submitted",nofdone) + end + report_svg("processing can be going on for a while") + inkscape:write("quit\n") + inkscape:close() + report_svg("processing %i pdf results",nofshapes) + for index in next, processed do + local svgfile = f_svgfile(index) + local pdffile = f_pdffile(index) + -- local fntdata = descriptions[indices[index]] + -- local bounds = fntdata and fntdata.boundingbox + local pdfdata = loaddata(pdffile) + if pdfdata and pdfdata ~= "" then + pdfshapes[index] = { + data = pdfdata, + -- x = bounds and bounds[1] or 0, + -- y = bounds and bounds[2] or 0, + } + end + remove(svgfile) + remove(pdffile) + end + local characters = tfmdata.characters + for k, v in next, characters do + local d = descriptions[k] + local i = d.index + if i then + local p = pdfshapes[i] + if p then + local w = d.width + local l = d.boundingbox[1] + local r = d.boundingbox[3] + p.scale = (r - l) / w + p.x = l + end + end + end + if not next(pdfshapes) then + report_svg("there are no converted shapes, fix your setup") + end + statistics.stoptiming() + if statistics.elapsedseconds then + report_svg("svg conversion time %s",statistics.elapsedseconds() or "-") + end + end + return pdfshapes + end + + initializesvg = function(tfmdata,kind,value) -- hm, always value + if value and otf.svgenabled then + local svg = tfmdata.properties.svg + local hash = svg and svg.hash + local timestamp = svg and svg.timestamp + if not hash then + return + end + local pdffile = containers.read(otf.pdfcache,hash) + local pdfshapes = pdffile and pdffile.pdfshapes + if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) then + -- the next test tries to catch errors in generic usage but of course can result + -- in running again and again + local svgfile = containers.read(otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata,otf.pdfcache.writable,hash) or { } + containers.write(otf.pdfcache, hash, { + pdfshapes = pdfshapes, + timestamp = timestamp, + }) + end + pdftovirtual(tfmdata,pdfshapes,"svg") + return true + end + end + + otfregister { + name = "svg", + description = "svg glyphs", + manipulators = { + base = initializesvg, + node = initializesvg, + } + } + +end + +-- This can be done differently e.g. with ffi and gm and we can share code anway. Using +-- batchmode in gm is not faster and as it accumulates we would need to flush all +-- individual shapes. But ... in context lmtx (and maybe the backport) we will use +-- a different and more efficient method anyway. I'm still wondering if I should +-- keep color code in generic. Maybe it should be optional. + +local initializepng do + + local otfpng = otf.png or { } + otf.png = otfpng + otf.pngenabled = true + + local report_png = logs.reporter("fonts","png conversion") + + local loaddata = io.loaddata + local savedata = io.savedata + local remove = os.remove + + local runner = sandbox and sandbox.registerrunner { + name = "otfpng", + program = "gm", + template = "convert -quality 100 temp-otf-png-shape.png temp-otf-png-shape.pdf > temp-otf-svg-shape.log", + -- reporter = report_png, + } + + if not runner then + -- + -- poor mans variant for generic: + -- + runner = function() + return os.execute("gm convert -quality 100 temp-otf-png-shape.png temp-otf-png-shape.pdf > temp-otf-svg-shape.log") + end + end + + -- Alternatively we can create a single pdf file with -adjoin and then pick up pages from + -- that file but creating thousands of small files is no fun either. + + local files = utilities.files + local openfile = files.open + local closefile = files.close + local setposition = files.setposition + local readstring = files.readstring + + function otfpng.topdf(pngshapes,filename) + if pngshapes and filename then + local pdfshapes = { } + local pngfile = "temp-otf-png-shape.png" + local pdffile = "temp-otf-png-shape.pdf" + local nofdone = 0 + local indices = sortedkeys(pngshapes) -- can be sparse + local nofindices = #indices + report_png("processing %i png containers",nofindices) + statistics.starttiming() + local filehandle = openfile(filename) + for i=1,nofindices do + local index = indices[i] + local entry = pngshapes[index] + -- local data = entry.data -- or placeholder + local offset = entry.o + local size = entry.s + local x = entry.x + local y = entry.y + local data = nil + if offset and size then + setposition(filehandle,offset) + data = readstring(filehandle,size) + savedata(pngfile,data) + runner() + data = loaddata(pdffile) + end + pdfshapes[index] = { +-- x = x ~= 0 and x or nil, +-- y = y ~= 0 and y or nil, + data = data, + } + nofdone = nofdone + 1 + if nofdone % 100 == 0 then + report_png("%i shapes processed",nofdone) + end + end + closefile(filehandle) + report_png("processing %i pdf results",nofindices) + remove(pngfile) + remove(pdffile) + statistics.stoptiming() + if statistics.elapsedseconds then + report_png("png conversion time %s",statistics.elapsedseconds() or "-") + end + return pdfshapes + end + end + + initializepng = function(tfmdata,kind,value) -- hm, always value + if value and otf.pngenabled then + local png = tfmdata.properties.png + local hash = png and png.hash + local timestamp = png and png.timestamp + if not hash then + return + end + local pdffile = containers.read(otf.pdfcache,hash) + local pdfshapes = pdffile and pdffile.pdfshapes + if not pdfshapes or pdffile.timestamp ~= timestamp then + local pngfile = containers.read(otf.pngcache,hash) + local filename = tfmdata.resources.filename + local pngshapes = pngfile and pngfile.pngshapes + pdfshapes = pngshapes and otfpng.topdf(pngshapes,filename) or { } + containers.write(otf.pdfcache, hash, { + pdfshapes = pdfshapes, + timestamp = timestamp, + }) + end + -- + pdftovirtual(tfmdata,pdfshapes,"png") + return true + end + end + + otfregister { + name = "sbix", + description = "sbix glyphs", + manipulators = { + base = initializepng, + node = initializepng, + } + } + + otfregister { + name = "cblc", + description = "cblc glyphs", + manipulators = { + base = initializepng, + node = initializepng, + } + } + +end + +do + + local function initializecolor(tfmdata,kind,value) + if value == "auto" then + return + initializeoverlay(tfmdata,kind,value) or + initializesvg (tfmdata,kind,value) or + initializepng (tfmdata,kind,value) + elseif value == "overlay" then + return initializeoverlay(tfmdata,kind,value) + elseif value == "svg" then + return initializesvg(tfmdata,kind,value) + elseif value == "png" or value == "bitmap" then + return initializepng(tfmdata,kind,value) + else + -- forget about it + end + end + + otfregister { + name = "color", + description = "color glyphs", + manipulators = { + base = initializecolor, + node = initializecolor, + } + } + +end + +-- Old stuff: + +do + + local startactualtext = nil + local stopactualtext = nil + + function otf.getactualtext(s) + if not startactualtext then + startactualtext = backends.codeinjections.startunicodetoactualtextdirect + stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect + end + return startactualtext(s), stopactualtext() + end + +end + |