font-imp-spacekerns.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['font-imp-spacekerns'] = {
    version   = 1.001,
    comment   = "companion to font-ini.mkiv and hand-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 end

-- This is an experiment. See font-ots.lua for original implementation.

local type, next = type, next
local insert, setmetatableindex = table.insert, table.setmetatableindex

local fonts              = fonts
local otf                = fonts.handlers.otf
local fontdata           = fonts.hashes.identifiers
local fontfeatures       = fonts.hashes.features
local otffeatures        = fonts.constructors.features.otf
local registerotffeature = otffeatures.register
local handlers           = otf.handlers
local setspacekerns      = nodes.injections.setspacekerns

function handlers.trigger_space_kerns(head,dataset,sequence,initialrl,font,attr)
    local features = fontfeatures[font]
    local enabled  = features and features.spacekern
    if enabled then
        setspacekerns(font,sequence) -- called quite often, each glyphrun
    end
    return head, enabled
end

local function hasspacekerns(data)
    local resources = data.resources
    local sequences = resources.sequences
    local validgpos = resources.features.gpos
    if validgpos and sequences then
        for i=1,#sequences do
            local sequence = sequences[i]
            local steps    = sequence.steps
            if steps then -- and sequence.features[tag] then
                local kind = sequence.type
                if kind == "gpos_pair" then -- or kind == "gpos_single" then
                    for i=1,#steps do
                        local step     = steps[i]
                        local coverage = step.coverage
                        local rules    = step.rules
                     -- if rules then
                     --     -- not now: analyze (simple) rules
                     -- elseif not coverage then
                     --     -- nothing to do
                     -- elseif kind == "gpos_single" then
                     --     -- maybe a message that we ignore
                     -- elseif kind == "gpos_pair" then
                        if coverage and not rules then
                            local format = step.format
                            if format == "move" or format == "kern" then
                                local kerns  = coverage[32]
                                if kerns then
                                    return true
                                end
                                for k, v in next, coverage do
                                    if v[32] then
                                        return true
                                    end
                                end
                            elseif format == "pair" then
                                local kerns  = coverage[32]
                                if kerns then
                                    for k, v in next, kerns do
                                        local one = v[1]
                                        if one and one ~= true then
                                            return true
                                        end
                                    end
                                end
                                for k, v in next, coverage do
                                    local kern = v[32]
                                    if kern then
                                        local one = kern[1]
                                        if one and one ~= true then
                                            return true
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    return false
end

otf.readers.registerextender {
    name   = "spacekerns",
    action = function(data)
        data.properties.hasspacekerns = hasspacekerns(data)
    end
}

local function newdata(t,k)
    local v = {
        left  = { },
        right = { },
        last  = 0,
        feat  = nil,
    }
    t[k] = v
    return v
end

local function spaceinitializer(tfmdata,value) -- attr
    local resources  = tfmdata.resources
    local spacekerns = resources and resources.spacekerns
    if value and spacekerns == nil then
        local rawdata    = tfmdata.shared and tfmdata.shared.rawdata
        local properties = rawdata.properties
        if properties and properties.hasspacekerns then
            local sequences = resources.sequences
            local validgpos = resources.features.gpos
            if validgpos and sequences then
                local data = setmetatableindex(newdata)
                for i=1,#sequences do
                    local sequence = sequences[i]
                    local steps    = sequence.steps
                    if steps then
                        -- we don't support space kerns in other features
                     -- local kern = sequence.features[tag]
                     -- if kern then
                        for tag, kern in next, sequence.features do

                            local d     = data[tag]
                            local left  = d.left
                            local right = d.right
                            local last  = d.last
                            local feat  = d.feat

                            local kind = sequence.type
                            if kind == "gpos_pair" then -- or kind == "gpos_single" then
                                if feat then
                                    for script, languages in next, kern do
                                        local f = feat[script]
                                        if f then
                                            for l in next, languages do
                                                f[l] = true
                                            end
                                        else
                                            feat[script] = languages
                                        end
                                    end
                                else
                                    feat = kern
    d.feat = feat
                                end
                                for i=1,#steps do
                                    local step     = steps[i]
                                    local coverage = step.coverage
                                    local rules    = step.rules
                                 -- if rules then
                                 --     -- not now: analyze (simple) rules
                                 -- elseif not coverage then
                                 --     -- nothing to do
                                 -- elseif kind == "gpos_single" then
                                 --     -- makes no sense in TeX
                                 -- elseif kind == "gpos_pair" then
                                    if coverage and not rules then
                                        local format = step.format
                                        if format == "move" or format == "kern" then
                                            local kerns  = coverage[32]
                                            if kerns then
                                                for k, v in next, kerns do
                                                    right[k] = v
                                                end
                                            end
                                            for k, v in next, coverage do
                                                local kern = v[32]
                                                if kern then
                                                    left[k] = kern
                                                end
                                            end
                                        elseif format == "pair" then
                                            local kerns  = coverage[32]
                                            if kerns then
                                                for k, v in next, kerns do
                                                    local one = v[1]
                                                    if one and one ~= true then
                                                        right[k] = one[3]
                                                    end
                                                end
                                            end
                                            for k, v in next, coverage do
                                                local kern = v[32]
                                                if kern then
                                                    local one = kern[1]
                                                    if one and one ~= true then
                                                        left[k] = one[3]
                                                    end
                                                end
                                            end
                                        end
                                    end
                                end
                                last = i
                            end
d.last = last
                        end
                    end
                end

                for tag, d in next, data do
                    local left  = d.left
                    local right = d.right
                    left  = next(left)  and left  or false
                    right = next(right) and right or false
                    if left or right then

                        local last  = d.last
                        local feat  = d.feat

                        if last > 0 then
                            local triggersequence = {
                                -- no steps, see (!!)
                                features = { [tag] = feat or { dflt = { dflt = true, } } },
                                flags    = noflags,
                                name     = "trigger_space_kerns",
                                order    = { tag },
                                type     = "trigger_space_kerns",
                                left     = left,
                                right    = right,
                            }
                            insert(sequences,last,triggersequence)
                            d.last = d.last + 1
                            spacekerns = true
                        end
                    end

                end
            end
        end
        if not spacekerns then
            spacekerns = false
        end
        resources.spacekerns = spacekerns
    end
    return spacekerns
end

registerotffeature {
    name         = "spacekern",
    description  = "space kern injection",
    default      = true,
    initializers = {
        node     = spaceinitializer,
    },
}