font-imp-effects.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['font-imp-effects'] = {
    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"
}

-- todo: pickup from goodies: if type(effect) then ...

local next, type, tonumber = next, type, tonumber
local is_boolean = string.is_boolean

local fonts              = fonts

local handlers           = fonts.handlers
local registerotffeature = handlers.otf.features.register
local registerafmfeature = handlers.afm.features.register

local settings_to_hash   = utilities.parsers.settings_to_hash_colon_too

local helpers            = fonts.helpers
local prependcommands    = helpers.prependcommands
local charcommand        = helpers.commands.char
local leftcommand        = helpers.commands.left
local rightcommand       = helpers.commands.right
local upcommand          = helpers.commands.up
local downcommand        = helpers.commands.down
local dummycommand       = helpers.commands.dummy

----- constructors       = fonts.constructors
----- getmathparameter   = constructors.getmathparameter
----- setmathparameter   = constructors.setmathparameter

local report_effect      = logs.reporter("fonts","effect")
local report_slant       = logs.reporter("fonts","slant")
local report_extend      = logs.reporter("fonts","extend")
local report_squeeze     = logs.reporter("fonts","squeeze")

local trace              = false

trackers.register("fonts.effect", function(v) trace = v end)
trackers.register("fonts.slant",  function(v) trace = v end)
trackers.register("fonts.extend", function(v) trace = v end)
trackers.register("fonts.squeeze",function(v) trace = v end)

local function initializeslant(tfmdata,value)
    value = tonumber(value)
    if not value then
        value =  0
    elseif value >  1 then
        value =  1
    elseif value < -1 then
        value = -1
    end
    if trace then
        report_slant("applying %0.3f",value)
    end
    tfmdata.parameters.slantfactor = value
end

local specification = {
    name        = "slant",
    description = "slant glyphs",
    initializers = {
        base = initializeslant,
        node = initializeslant,
    }
}

registerotffeature(specification)
registerafmfeature(specification)

local function initializeextend(tfmdata,value)
    value = tonumber(value)
    if not value then
        value =  0
    elseif value >  10 then
        value =  10
    elseif value < -10 then
        value = -10
    end
    if trace then
        report_extend("applying %0.3f",value)
    end
    tfmdata.parameters.extendfactor = value
end

local specification = {
    name        = "extend",
    description = "scale glyphs horizontally",
    initializers = {
        base = initializeextend,
        node = initializeextend,
    }
}

registerotffeature(specification)
registerafmfeature(specification)

local function initializesqueeze(tfmdata,value)
    value = tonumber(value)
    if not value then
        value =  0
    elseif value >  10 then
        value =  10
    elseif value < -10 then
        value = -10
    end
    if trace then
        report_squeeze("applying %0.3f",value)
    end
    tfmdata.parameters.squeezefactor = value
end

local specification = {
    name        = "squeeze",
    description = "scale glyphs vertically",
    initializers = {
        base = initializesqueeze,
        node = initializesqueeze,
    }
}

registerotffeature(specification)
registerafmfeature(specification)

local effects = {
    inner   = 0,
    normal  = 0,
    outer   = 1,
    outline = 1,
    both    = 2,
    hidden  = 3,
}

local function initializeeffect(tfmdata,value)
    local spec
    if type(value) == "number" then
        spec = { width = value }
    else
        spec = settings_to_hash(value)
    end
    local effect = spec.effect or "both"
    local width  = tonumber(spec.width) or 0
    local mode   = effects[effect]
    if not mode then
        report_effect("invalid effect %a",effect)
    elseif width == 0 and mode == 0 then
        report_effect("invalid width %a for effect %a",width,effect)
    else
        local parameters = tfmdata.parameters
        local properties = tfmdata.properties
        parameters.mode  = mode
        parameters.width = width * 1000
        if is_boolean(spec.auto) == true then
            local squeeze = 1 - width/20
            local average = (1 - squeeze) * width * 100
            spec.squeeze  = squeeze
            spec.extend   = 1 + width/2
            spec.wdelta   = average
            spec.hdelta   = average/2
            spec.ddelta   = average/2
            spec.vshift   = average/2
        end
        local factor  = tonumber(spec.factor)  or 0
        local hfactor = tonumber(spec.hfactor) or factor
        local vfactor = tonumber(spec.vfactor) or factor
        local delta   = tonumber(spec.delta)   or 1
        local wdelta  = tonumber(spec.wdelta)  or delta
        local hdelta  = tonumber(spec.hdelta)  or delta
        local ddelta  = tonumber(spec.ddelta)  or hdelta
        local vshift  = tonumber(spec.vshift)  or 0
        local slant   = spec.slant
        local extend  = spec.extend
        local squeeze = spec.squeeze
        if slant then
            initializeslant(tfmdata,slant)
        end
        if extend then
            initializeextend(tfmdata,extend)
        end
        if squeeze then
            initializesqueeze(tfmdata,squeeze)
        end
        properties.effect = {
            effect  = effect,
            width   = width,
            factor  = factor,
            hfactor = hfactor,
            vfactor = vfactor,
            wdelta  = wdelta,
            hdelta  = hdelta,
            ddelta  = ddelta,
            vshift  = vshift,
            slant   = tfmdata.parameters.slantfactor,
            extend  = tfmdata.parameters.extendfactor,
            squeeze = tfmdata.parameters.squeezefactor,
        }
    end
end

local rules = {
    "RadicalRuleThickness",
    "OverbarRuleThickness",
    "FractionRuleThickness",
    "UnderbarRuleThickness",
}

-- local commands = char.commands
-- if commands then
--     local command = commands[1]
--     if command and command[1] == "right" then
--         commands[1] = rightcommand[command[2]-snap]
--     end
-- end

local function setmathparameters(tfmdata,characters,mathparameters,dx,dy,squeeze)
    if delta ~= 0 then
        for i=1,#rules do
            local name  = rules[i]
            local value = mathparameters[name]
            if value then
               mathparameters[name] = (squeeze or 1) * (value + dx)
            end
        end
    end
end

local function setmathcharacters(tfmdata,characters,mathparameters,dx,dy,squeeze,wdelta,hdelta,ddelta)

    local function wdpatch(char)
        if wsnap ~= 0 then
            char.width  = char.width + wdelta/2
        end
    end

    local function htpatch(char)
        if hsnap ~= 0 then
            local height = char.height
            if height then
                char.height = char.height + 2 * dy
            end
        end
    end

    local character = characters[0x221A]

    if character and character.next then
-- print("base char",0x221A,table.sequenced(character))
        local char = character
        local next = character.next
        wdpatch(char)
        htpatch(char)
        while next do
            char = characters[next]
            wdpatch(char)
            htpatch(char)
-- print("next char",next,table.sequenced(char))
            next = char.next
        end
        if char then
            local v = char.vert_variants
            if v then
                local top = v[#v]
                if top then
                    local char = characters[top.glyph]
-- print("top char",top.glyph,table.sequenced(char))
                    htpatch(char)
                end
            end
        end
    end
end

-- local show_effect = { "lua", function(f,c)
--     report_effect("font id %i, char %C",f,c)
--     inspect(fonts.hashes.characters[f][c])
-- end }

-- local show_effect = { "lua", "print('!')" }

local function manipulateeffect(tfmdata)
    local effect = tfmdata.properties.effect
    if effect then
        local characters     = tfmdata.characters
        local parameters     = tfmdata.parameters
        local mathparameters = tfmdata.mathparameters
        local multiplier     = effect.width * 100
        local factor         = parameters.factor
        local hfactor        = parameters.hfactor
        local vfactor        = parameters.vfactor
        local wdelta         = effect.wdelta * hfactor * multiplier
        local hdelta         = effect.hdelta * vfactor * multiplier
        local ddelta         = effect.ddelta * vfactor * multiplier
        local vshift         = effect.vshift * vfactor * multiplier
        local squeeze        = effect.squeeze
        local hshift         = wdelta / 2
        local dx             = multiplier * vfactor
        local dy             = vshift
        local factor         = (1 + effect.factor)  * factor
        local hfactor        = (1 + effect.hfactor) * hfactor
        local vfactor        = (1 + effect.vfactor) * vfactor
        local vshift         = vshift ~= 0 and upcommand[vshift] or false
        for unicode, character in next, characters do
            local oldwidth  = character.width
            local oldheight = character.height
            local olddepth  = character.depth
            if oldwidth and oldwidth > 0 then
                character.width = oldwidth + wdelta
                local commands = character.commands
                local hshift   = rightcommand[hshift]
                if vshift then
                    if commands then
                        prependcommands ( commands,
-- show_effect,
                            hshift,
                            vshift
                        )
                    else
                        character.commands = {
-- show_effect,
                            hshift,
                            vshift,
                            charcommand[unicode]
                        }
                    end
                else
                    if commands then
                        prependcommands ( commands,
-- show_effect,
                            hshift
                        )
                    else
                        character.commands = {
-- show_effect,
                            hshift,
                            charcommand[unicode]
                        }
                    end
                end
            end
            if oldheight and oldheight > 0 then
               character.height = oldheight + hdelta
            end
            if olddepth and olddepth > 0 then
               character.depth = olddepth + ddelta
            end
        end
        if mathparameters then
            setmathparameters(tfmdata,characters,mathparameters,dx,dy,squeeze)
            setmathcharacters(tfmdata,characters,mathparameters,dx,dy,squeeze,wdelta,hdelta,ddelta)
        end
        parameters.factor  = factor
        parameters.hfactor = hfactor
        parameters.vfactor = vfactor
        if trace then
            report_effect("applying")
            report_effect("  effect  : %s", effect.effect)
            report_effect("  width   : %s => %s", effect.width,  multiplier)
            report_effect("  factor  : %s => %s", effect.factor, factor )
            report_effect("  hfactor : %s => %s", effect.hfactor,hfactor)
            report_effect("  vfactor : %s => %s", effect.vfactor,vfactor)
            report_effect("  wdelta  : %s => %s", effect.wdelta, wdelta)
            report_effect("  hdelta  : %s => %s", effect.hdelta, hdelta)
            report_effect("  ddelta  : %s => %s", effect.ddelta, ddelta)
        end
    end
end

local specification = {
    name        = "effect",
    description = "apply effects to glyphs",
    initializers = {
        base = initializeeffect,
        node = initializeeffect,
    },
    manipulators = {
        base = manipulateeffect,
        node = manipulateeffect,
    },
}

registerotffeature(specification)
registerafmfeature(specification)

local function initializeoutline(tfmdata,value)
    value = tonumber(value)
    if not value then
        value = 0
    else
        value = tonumber(value) or 0
    end
    local parameters = tfmdata.parameters
    local properties = tfmdata.properties
    parameters.mode  = effects.outline
    parameters.width = value * 1000
    properties.effect = {
        effect = effect,
        width  = width,
    }
end

local specification = {
    name        = "outline",
    description = "outline glyphs",
    initializers = {
        base = initializeoutline,
        node = initializeoutline,
    }
}

registerotffeature(specification)
registerafmfeature(specification)