attr-lay.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['attr-lay'] = {
    version   = 1.001,
    comment   = "companion to attr-lay.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- layers (ugly code, due to no grouping and such); currently we use exclusive layers
-- but when we need it stacked layers might show up too; the next function based
-- approach can be replaced by static (metatable driven) resolvers

-- maybe use backends.registrations here too

local type = type
local insert, remove = table.insert, table.remove

local attributes          = attributes
local nodes               = nodes
local utilities           = utilities
local logs                = logs
local backends            = backends

local context             = context
local interfaces          = interfaces
local tex                 = tex

local implement           = interfaces.implement

local allocate            = utilities.storage.allocate
local setmetatableindex   = table.setmetatableindex
local formatters          = string.formatters

local report_viewerlayers = logs.reporter("viewerlayers")

-- todo: document this but first reimplement this as it reflects the early
-- days of luatex / mkiv and we have better ways now

-- nb: attributes: color etc is much slower than normal (marks + literals) but ...
-- nb. too many "0 g"s
-- nb: more local tables

attributes.viewerlayers = attributes.viewerlayers or { }
local viewerlayers      = attributes.viewerlayers

local variables         = interfaces.variables
local v_local           = variables["local"]
local v_global          = variables["global"]
local v_start           = variables["start"]
local v_yes             = variables["yes"]

local a_viewerlayer     = attributes.private("viewerlayer")

viewerlayers            = viewerlayers            or { }
viewerlayers.data       = allocate()
viewerlayers.registered = viewerlayers.registered or { }
viewerlayers.values     = viewerlayers.values     or { }
viewerlayers.scopes     = viewerlayers.scopes     or { }
viewerlayers.listwise   = allocate()
viewerlayers.attribute  = a_viewerlayer
viewerlayers.supported  = true
viewerlayers.hasorder   = true

local states            = attributes.states
local enableaction      = nodes.tasks.enableaction
local disableaction     = nodes.tasks.disableaction
local nodeinjections    = backends.nodeinjections
local codeinjections    = backends.codeinjections

local texsetattribute   = tex.setattribute
local texgetattribute   = tex.getattribute
local texsettokenlist   = tex.settoks
local unsetvalue        = attributes.unsetvalue

local data              = viewerlayers.data
local values            = viewerlayers.values
local listwise          = viewerlayers.listwise
local registered        = viewerlayers.registered
local scopes            = viewerlayers.scopes

local f_stamp           = formatters["%s"]

storage.register("attributes/viewerlayers/registered", registered, "attributes.viewerlayers.registered")
storage.register("attributes/viewerlayers/values",     values,     "attributes.viewerlayers.values")
storage.register("attributes/viewerlayers/scopes",     scopes,     "attributes.viewerlayers.scopes")

local layerstacker = utilities.stacker.new("layers") -- experiment

layerstacker.mode  = "stack"
layerstacker.unset = attributes.unsetvalue

viewerlayers.resolve_reset = layerstacker.resolve_reset
viewerlayers.resolve_begin = layerstacker.resolve_begin
viewerlayers.resolve_step  = layerstacker.resolve_step
viewerlayers.resolve_end   = layerstacker.resolve_end

-- stacked

local function startlayer(...) startlayer = nodeinjections.startlayer return startlayer(...) end
local function stoplayer (...) stoplayer  = nodeinjections.stoplayer  return stoplayer (...) end

local function extender(viewerlayers,key)
    if viewerlayers.supported and key == "none" then
        local d = stoplayer()
        viewerlayers.none = d
        return d
    end
end

local function reviver(data,n)
    if viewerlayers.supported then
        local v = values[n]
        if v then
            local d = startlayer(v)
            data[n] = d
            return d
        else
            report_viewerlayers("error: unknown reference %a",tostring(n))
        end
    end
end

setmetatableindex(viewerlayers,extender)
setmetatableindex(viewerlayers.data,reviver)

--  !!!! TEST CODE !!!!

layerstacker.start  = function(...) local f = nodeinjections.startstackedlayer  layerstacker.start  = f return f(...) end
layerstacker.stop   = function(...) local f = nodeinjections.stopstackedlayer   layerstacker.stop   = f return f(...) end
layerstacker.change = function(...) local f = nodeinjections.changestackedlayer layerstacker.change = f return f(...) end

local function initializer(...)
    return states.initialize(...)
end

attributes.viewerlayers.handler = nodes.installattributehandler {
    name        = "viewerlayer",
    namespace   = viewerlayers,
    initializer = initializer,
    finalizer   = states.finalize,
    processor   = states.stacker,
 -- processor   = states.stacked,
}

local stack, enabled, global = { }, false, false

function viewerlayers.enable(value)
    if value == false or not viewerlayers.supported then
        if enabled then
            disableaction("shipouts","attributes.viewerlayers.handler")
        end
        enabled = false
    else
        if not enabled then
            enableaction("shipouts","attributes.viewerlayers.handler")
        end
        enabled = true
    end
end

function viewerlayers.forcesupport(value)
    viewerlayers.supported = value
    report_viewerlayers("viewerlayers are %ssupported",value and "" or "not ")
    viewerlayers.enable(value)
end

local function register(name,lw) -- if not inimode redefine data[n] in first call
    if not enabled then
        viewerlayers.enable(true)
    end
    local stamp = f_stamp(name)
    local n = registered[stamp]
    if not n then
        n = #values + 1
        values[n] = name
        registered[stamp] = n
        listwise[n] = lw or false -- lw forces a used
    end
    return registered[stamp] -- == n
end

viewerlayers.register = register

function viewerlayers.setfeatures(hasorder)
    viewerlayers.hasorder = hasorder
end

local usestacker = true -- new, experimental

function viewerlayers.start(name)
    local a
    if usestacker then
        a = layerstacker.push(register(name) or unsetvalue)
    else
        insert(stack,texgetattribute(a_viewerlayer))
        a = register(name) or unsetvalue
    end
    if global or scopes[name] == v_global then
        scopes[a] = v_global -- messy but we don't know the attributes yet
        texsetattribute("global",a_viewerlayer,a)
    else
        texsetattribute(a_viewerlayer,a)
    end
    -- or macro
    texsettokenlist("currentviewerlayertoks",name)
end

function viewerlayers.stop()
    local a
    if usestacker then
        a = layerstacker.pop()
    else
        a = remove(stack)
    end
    if not a then
        -- error
    elseif a >= 0 then
        if global or scopes[a] == v_global then
            texsetattribute("global",a_viewerlayer,a)
        else
            texsetattribute(a_viewerlayer,a)
        end
        texsettokenlist("currentviewerlayertoks",values[a] or "")
    else
        if global or scopes[a] == v_global then
            texsetattribute("global",a_viewerlayer,unsetvalue)
        else
            texsetattribute(a_viewerlayer,unsetvalue)
        end
        texsettokenlist("currentviewerlayertoks","")
    end
end

function viewerlayers.define(settings)
    local tag = settings.tag
    if not tag or tag == "" then
        -- error
    elseif not scopes[tag] then -- prevent duplicates
        local title = settings.title
        if not title or title == "" then
            settings.title = tag
        end
        scopes[tag] = settings.scope or v_local
        codeinjections.defineviewerlayer(settings)
    end
end

function viewerlayers.definedlayoutcomponent(tag)
    viewerlayers.define {
        tag       = tag,
        title     = utilities.strings.nice(tag),
        visible   = v_start,
        editable  = v_yes,
        printable = v_yes,
    }
    return register(tag,true) -- true forces a use
end

function viewerlayers.cleanup()
    layerstacker.clean()
    -- todo
end

implement {
    name      = "cleanuplayers",
    actions   = viewerlayers.cleanup
}

implement {
    name      = "defineviewerlayer",
    actions   = viewerlayers.define,
    arguments = {
        {
            { "tag" },
            { "title" },
            { "visible" },
            { "editable" },
            { "export" },
            { "printable" },
            { "scope" },
        },
        true
    }
}

implement {
    name      = "definedlayoutcomponent",
    actions   = { viewerlayers.definedlayoutcomponent, context },
    arguments = "string"
}

implement {
    name      = "startviewerlayer",
    actions   = viewerlayers.start,
    arguments = "string",
}

implement {
    name      = "stopviewerlayer",
    actions   = viewerlayers.stop
}

implement {
    name      = "registeredviewerlayer",
    actions   = { register, context },
    arguments = { "string", true } -- true forces a use
}