diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/font-ogr.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/font-ogr.lua | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/font-ogr.lua b/Master/texmf-dist/tex/context/base/mkiv/font-ogr.lua new file mode 100644 index 00000000000..b45728c6cc1 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkiv/font-ogr.lua @@ -0,0 +1,670 @@ +if not modules then modules = { } end modules ['font-ogr'] = { + 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" +} + +-- Here we deal with graphic variants and for now also color support ends up here +-- but that might change. It's lmtx only code. + +if not context then + return +elseif CONTEXTLMTXMODE == 0 then + return +end + +local tostring, tonumber, next, type = tostring, tonumber, next, type +local round, max, mod, div = math.round, math.max, math.mod, math.div +local find = string.find +local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash +local utfbyte = utf.byte +local formatters = string.formatters +local settings_to_hash_strict, settings_to_array = utilities.parsers.settings_to_hash_strict, utilities.parsers.settings_to_array + +local otf = fonts.handlers.otf +local otfregister = otf.features.register +otf.svgenabled = true +otf.pngenabled = true + +-- Just to remind me ... rewritten around the time this was posted on YT which +-- was also around the 2019 ctx meeting: +-- +-- Gavin Harrison - "Threatening War" by The Pineapple Thief +-- https://www.youtube.com/watch?v=ENF9wth4kwM + +-- todo: svg color plugin +-- todo: get rid of double cm in svg (tricky as also elsewhere) +-- todo: png offsets (depth) +-- todo: maybe collapse indices so that we have less files (harder to debug) +-- todo: manage (read: assign) font id's in lua so we know in advance + +-- what here and what in backend ... + +do + + -- This is a prelude to something better but I'm still experimenting. + + local dropins = { } + fonts.dropins = dropins + local droppedin = 0 + local identifiers = fonts.hashes.identifiers + + function dropins.nextid() + droppedin = droppedin - 1 + return droppedin + end + + -- todo: pass specification table instead + + function dropins.provide(method,t_tfmdata,indexdata,...) + local droppedin = dropins.nextid() + local t_characters = t_tfmdata.characters + local t_descriptions = t_tfmdata.descriptions + local t_properties = t_tfmdata.properties + local d_tfmdata = setmetatableindex({ },t_tfmdata) + local d_properties = setmetatableindex({ },t_properties) + d_tfmdata.properties = d_properties + local d_characters = { } -- setmetatableindex({ },t_characters) -- hm, index vs unicode + local d_descriptions = { } -- setmetatableindex({ },t_descriptions) -- hm, index vs unicode + d_tfmdata.characters = d_characters + d_tfmdata.descriptions = d_descriptions + d_properties.instance = - droppedin -- will become an extra element in the hash + t_properties.virtualized = true + identifiers[droppedin] = d_tfmdata + local fonts = t_tfmdata.fonts or { } + t_tfmdata.fonts = fonts + d_properties.format = "type3" + d_properties.method = method + d_properties.indexdata = { indexdata, ... } -- can take quite some memory + local slot = #fonts + 1 + fonts[slot] = { id = droppedin } + return slot, droppedin, d_tfmdata, d_properties + end + + function dropins.clone(method,tfmdata,shapes,...) -- by index + if method and shapes then + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local droppedin, tfmdrop, dropchars, dropdescs, colrshapes + local idx = 255 + local slot = 0 + for k, v in next, characters do + local index = v.index + if index then + local description = descriptions[k] + if description then + local shape = shapes[index] + if shape then + if idx >= 255 then + idx = 1 + colrshapes = setmetatableindex({ },shapes) + slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes) + dropchars = tfmdrop.characters + dropdescs = tfmdrop.descriptions + else + idx = idx + 1 + end + colrshapes[idx] = shape -- so not: description + -- todo: prepend + v.commands = { { "slot", slot, idx } } + -- hack to prevent that type 3 also gets 'use' flags .. todo + local c = { commands = false, index = idx, dropin = tfmdrop } + local d = { } -- { index = idx, dropin = tfmdrop } + setmetatableindex(c,v) + setmetatableindex(d,description) + dropchars[idx] = c + dropdescs[idx] = d -- not needed + end + end + end + end + else + -- error + end + end + + function dropins.swap(method,tfmdata,shapes,...) -- by unicode + if method and shapes then + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local droppedin, tfmdrop, dropchars, dropdescs, colrshapes + local idx = 255 + local slot = 0 + -- we can have a variant where shaped are by unicode and not by index + for k, v in next, characters do + local description = descriptions[k] + if description then + local shape = shapes[k] + if shape then + if idx >= 255 then + idx = 1 + colrshapes = setmetatableindex({ },shapes) + slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes) + dropchars = tfmdrop.characters + dropdescs = tfmdrop.descriptions + else + idx = idx + 1 + end + colrshapes[idx] = shape -- so not: description + -- todo: prepend + v.commands = { { "slot", slot, idx } } + -- hack to prevent that type 3 also gets 'use' flags .. todo + local c = { commands = false, index = idx, dropin = tfmdrop } + local d = { } -- index = idx, dropin = tfmdrop } + setmetatableindex(c,v) + setmetatableindex(d,description) + dropchars[idx] = c + dropdescs[idx] = d -- not needed + end + end + end + else + -- error + end + end + +end + +do -- this will move to its own module + + local dropins = fonts.dropins + + local shapes = setmetatableindex(function(t,k) + local v = { + glyphs = { }, + parameters = { + units = 1000 + }, + } + t[k] = v + return v + end) + + function dropins.registerglyphs(parameters) + local category = parameters.name + local target = shapes[category].parameters + for k, v in next, parameters do + if k ~= "glyphs" then + target[k] = v + end + end + end + + function dropins.registerglyph(parameters) + local category = parameters.category + local unicode = parameters.unicode + local private = parameters.private + local unichar = parameters.unichar + if private then + unicode = fonts.helpers.newprivateslot(private) + elseif type(unichar) == "string" then + unicode = utfbyte(unichar) + else + local unitype = type(unicode) + if unitype == "string" then + local uninumber = tonumber(unicode) + if uninumber then + unicode = round(uninumber) + else + unicode = utfbyte(unicode) + end + elseif unitype == "number" then + unicode = round(unicode) + end + end + if unicode then + parameters.unicode = unicode + -- print(category,unicode) + shapes[category].glyphs[unicode] = parameters + else + -- error + end + end + + -- local function hascolorspec(t) + -- if (t.color or "") ~= "" then + -- return true + -- elseif (t.fillcolor or "") ~= "" then + -- return true + -- elseif (t.drawcolor or "") ~= "" then + -- return true + -- elseif (t.linecolor or "") ~= "" then + -- return true + -- else + -- return false + -- end + -- end + + local function hascolorspec(t) + for k, v in next, t do + if find(k,"color") then + return true + end + end + return false + end + + local function initializemps(tfmdata,kind,value) + if value then + local specification = settings_to_hash_strict(value) + if not specification or not next(specification) then + specification = { category = value } + end + -- todo: multiple categories but then maybe also different + -- clones because of the units .. for now we assume the same + -- units + local category = specification.category + if category and category ~= "" then + local categories = settings_to_array(category) + local usedshapes = nil + local index = 0 + local spread = tonumber(specification.spread or 0) + local hascolor = hascolorspec(specification) + specification.spread = spread -- now a number + for i=1,#categories do + local category = categories[i] + local mpsshapes = shapes[category] + if mpsshapes then + local properties = tfmdata.properties + local parameters = tfmdata.parameters + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local mpsparameters = mpsshapes.parameters + local units = mpsparameters.units or 1000 + local defaultwidth = mpsparameters.width or 0 + local defaultheight = mpsparameters.height or 0 + local defaultdepth = mpsparameters.depth or 0 + local usecolor = mpsparameters.usecolor + local spread = spread * units + local defaultcode = mpsparameters.code or "" + local scale = parameters.size / units + if hascolor then + -- the graphic has color + usecolor = false + else + -- do whatever is specified + end + usedshapes = usedshapes or { + instance = "simplefun", + units = units, + usecolor = usecolor, + specification = specification, + shapes = mpsshapes, + } + -- todo: deal with extensibles and more properties + for unicode, shape in sortedhash(mpsshapes.glyphs) do + -- local oldc = characters[unicode] + -- if oldc then + index = index + 1 -- todo: somehow we end up with 2 as first entry after 0 + local wd = shape.width or defaultwidth + local ht = shape.height or defaultheight + local dp = shape.depth or defaultdepth + local newc = { + index = index, -- into usedshapes + width = scale * (wd + spread), + height = scale * ht, + depth = scale * dp, + unicode = unicode, + } + -- + characters [unicode] = newc + descriptions[unicode] = newc + -- + usedshapes[unicode] = shape.code or defaultcode + -- end + end + end + end + if usedshapes then + -- todo: different font when units and usecolor changes, maybe move into loop + -- above + dropins.swap("mps",tfmdata,usedshapes) + end + end + end + end + + -- This kicks in quite late, after features have been checked. So if needed + -- substitutions need to be defined with force. + + otfregister { + name = "metapost", + description = "metapost glyphs", + manipulators = { + base = initializemps, + node = initializemps, + } + } + +end + +-- This sits here for historcal reasons so for now we keep it here. + +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 + +-- This is also somewhat specific. + +local sharedpalettes do + + 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 + +end + +local initializeoverlay do + + -- we should use the proper interface instead but for now: + + local colors = attributes.colors + local rgbtocmyk = colors.rgbtocmyk + + local f_cmyk = formatters["%.3N %.3f %.3N %.3N k"] + local f_rgb = formatters["%.3N %.3f %.3N rg"] + local f_gray = formatters["%.3N g"] + + local function convert(t,k) + local v = { } + local m = colors.model + for i=1,#k do + local p = k[i] + local r, g, b = p[1]/255, p[2]/255, p[3]/255 + if r == g and g == b then + p = f_gray(r) + elseif m == "cmyk" then + p = f_cmyk(rgbtocmyk(r,g,b)) + else + p = f_rgb(r,g,b) + end + v[i] = p + end + t[k] = v + return v + end + + initializeoverlay = function(tfmdata,kind,value) -- we really need the id ... todo + 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 droppedin, tfmdrop, dropchars, dropdescs, colrshapes + local idx = 255 + local slot = 0 + -- + -- todo: delay + -- + for k, v in next, characters do + local index = v.index + if index then + local description = descriptions[k] + if description then + local colorlist = description.colors + if colorlist then + if idx >= 255 then + idx = 1 + colrshapes = { } + slot, droppedin, tfmdrop = fonts.dropins.provide("color",tfmdata,colrshapes,colorvalues) + dropchars = tfmdrop.characters + dropdescs = tfmdrop.descriptions + else + idx = idx + 1 + end + -- + colrshapes[idx] = description + -- todo: use extender + local u = { "use", 0 } + for i=1,#colorlist do + u[i+2] = colorlist[i].slot + end + v.commands = { u, { "slot", slot, idx } } + -- hack to prevent that type 3 also gets 'use' flags .. todo + local c = { commands = false, index = idx, dropin = tfmdata } + local d = { } -- index = idx, dropin = tfmdrop + setmetatableindex(c,v) + setmetatableindex(d,description) + dropchars[idx] = c + dropdescs[idx] = d -- not needed + end + end + end + end + return true + end + end + end + + fonts.handlers.otf.features.register { + name = "colr", + description = "color glyphs", + manipulators = { + base = initializeoverlay, + node = initializeoverlay, + } + } + +end + +local initializesvg do + + local report_svg = logs.reporter("fonts","svg") + + local cached = true -- maybe always false (after i've optimized the lot) + + directives.register("fonts.svg.cached", function(v) cached = v end) + + initializesvg = function(tfmdata,kind,value) -- hm, always value + if value then + local properties = tfmdata.properties + local svg = properties.svg + local hash = svg and svg.hash + local timestamp = svg and svg.timestamp + if not hash then + return + end + local shapes = nil + local method = nil + local enforce = attributes.colors.model == "cmyk" + if cached and not enforce then + -- we need a different hash than for mkiv, so we append: + local pdfhash = hash .. "-svg" + local pdffile = containers.read(otf.pdfcache,pdfhash) + local pdfshapes = pdffile and pdffile.pdfshapes + local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf")) + if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then + local svgfile = containers.read(otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg,tfmdata.parameters.units) or { } + -- look at ocl: we should store scale and x and y + containers.write(otf.pdfcache, pdfhash, { + pdfshapes = pdfshapes, + timestamp = timestamp, + }) + end + shapes = pdfshapes + method = "pdf" + else + local mpsfile = containers.read(otf.mpscache,hash) + local mpsshapes = mpsfile and mpsfile.mpsshapes + if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then + local svgfile = containers.read(otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + -- still suboptimal + mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg,tfmdata.parameters.units) or { } + if enforce then + -- cheap conversion, no black component generation + mpsshapes.preamble = "interim svgforcecmyk := 1;" + end + containers.write(otf.mpscache, hash, { + mpsshapes = mpsshapes, + timestamp = timestamp, + }) + end + shapes = mpsshapes + method = "mps" + end + if shapes then + shapes.fixdepth = value == "fixdepth" + fonts.dropins.clone(method,tfmdata,shapes) + end + return true + end + end + + otfregister { + name = "svg", + description = "svg glyphs", + manipulators = { + base = initializesvg, + node = initializesvg, + } + } + +end + +local initializepng do + + -- If this is really critical we can also use a pdf file as cache but I don't expect + -- png fonts to remain used. + + local colors = attributes.colors + + local report_png = logs.reporter("fonts","png conversion") + + initializepng = function(tfmdata,kind,value) -- hm, always value + if value then + local properties = tfmdata.properties + local png = properties.png + local hash = png and png.hash + local timestamp = png and png.timestamp + if not hash then + return + end + local pngfile = containers.read(otf.pngcache,hash) + local pngshapes = pngfile and pngfile.pngshapes + if pngshapes then + if colors.model == "cmyk" then + pngshapes.enforcecmyk = true + end + fonts.dropins.clone("png",tfmdata,pngshapes) + end + 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 + + -- I need to check jpeg and such but will do that when I run into + -- it. + + 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 |