attr-lay.lua /size: 9278 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['attr-lay'] = {
2    version   = 1.001,
3    comment   = "companion to attr-lay.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- layers (ugly code, due to no grouping and such); currently we use exclusive layers
10-- but when we need it stacked layers might show up too; the next function based
11-- approach can be replaced by static (metatable driven) resolvers
12
13-- maybe use backends.registrations here too
14
15local type = type
16local insert, remove = table.insert, table.remove
17
18local attributes          = attributes
19local nodes               = nodes
20local utilities           = utilities
21local logs                = logs
22local backends            = backends
23
24local context             = context
25local interfaces          = interfaces
26local tex                 = tex
27
28local implement           = interfaces.implement
29
30local allocate            = utilities.storage.allocate
31local setmetatableindex   = table.setmetatableindex
32local formatters          = string.formatters
33
34local report_viewerlayers = logs.reporter("viewerlayers")
35
36-- todo: document this but first reimplement this as it reflects the early
37-- days of luatex / mkiv and we have better ways now
38
39-- nb: attributes: color etc is much slower than normal (marks + literals) but ...
40-- nb. too many "0 g"s
41-- nb: more local tables
42
43attributes.viewerlayers = attributes.viewerlayers or { }
44local viewerlayers      = attributes.viewerlayers
45
46local variables         = interfaces.variables
47local v_local           = variables["local"]
48local v_global          = variables["global"]
49local v_start           = variables["start"]
50local v_yes             = variables["yes"]
51
52local a_viewerlayer     = attributes.private("viewerlayer")
53
54viewerlayers            = viewerlayers            or { }
55viewerlayers.data       = allocate()
56viewerlayers.registered = viewerlayers.registered or { }
57viewerlayers.values     = viewerlayers.values     or { }
58viewerlayers.scopes     = viewerlayers.scopes     or { }
59viewerlayers.listwise   = allocate()
60viewerlayers.attribute  = a_viewerlayer
61viewerlayers.supported  = true
62viewerlayers.hasorder   = true
63
64local states            = attributes.states
65local enableaction      = nodes.tasks.enableaction
66local disableaction     = nodes.tasks.disableaction
67local nodeinjections    = backends.nodeinjections
68local codeinjections    = backends.codeinjections
69
70local texsetattribute   = tex.setattribute
71local texgetattribute   = tex.getattribute
72local texsettokenlist   = tex.settoks
73local unsetvalue        = attributes.unsetvalue
74
75local data              = viewerlayers.data
76local values            = viewerlayers.values
77local listwise          = viewerlayers.listwise
78local registered        = viewerlayers.registered
79local scopes            = viewerlayers.scopes
80
81local f_stamp           = formatters["%s"]
82
83storage.register("attributes/viewerlayers/registered", registered, "attributes.viewerlayers.registered")
84storage.register("attributes/viewerlayers/values",     values,     "attributes.viewerlayers.values")
85storage.register("attributes/viewerlayers/scopes",     scopes,     "attributes.viewerlayers.scopes")
86
87local layerstacker = utilities.stacker.new("layers") -- experiment
88
89layerstacker.mode  = "stack"
90layerstacker.unset = attributes.unsetvalue
91
92viewerlayers.resolve_reset = layerstacker.resolve_reset
93viewerlayers.resolve_begin = layerstacker.resolve_begin
94viewerlayers.resolve_step  = layerstacker.resolve_step
95viewerlayers.resolve_end   = layerstacker.resolve_end
96
97-- stacked
98
99local function startlayer(...) startlayer = nodeinjections.startlayer return startlayer(...) end
100local function stoplayer (...) stoplayer  = nodeinjections.stoplayer  return stoplayer (...) end
101
102local function extender(viewerlayers,key)
103    if viewerlayers.supported and key == "none" then
104        local d = stoplayer()
105        viewerlayers.none = d
106        return d
107    end
108end
109
110local function reviver(data,n)
111    if viewerlayers.supported then
112        local v = values[n]
113        if v then
114            local d = startlayer(v)
115            data[n] = d
116            return d
117        else
118            report_viewerlayers("error: unknown reference %a",tostring(n))
119        end
120    end
121end
122
123setmetatableindex(viewerlayers,extender)
124setmetatableindex(viewerlayers.data,reviver)
125
126--  !!!! TEST CODE !!!!
127
128layerstacker.start  = function(...) local f = nodeinjections.startstackedlayer  layerstacker.start  = f return f(...) end
129layerstacker.stop   = function(...) local f = nodeinjections.stopstackedlayer   layerstacker.stop   = f return f(...) end
130layerstacker.change = function(...) local f = nodeinjections.changestackedlayer layerstacker.change = f return f(...) end
131
132local function initializer(...)
133    return states.initialize(...)
134end
135
136attributes.viewerlayers.handler = nodes.installattributehandler {
137    name        = "viewerlayer",
138    namespace   = viewerlayers,
139    initializer = initializer,
140    finalizer   = states.finalize,
141    processor   = states.stacker,
142 -- processor   = states.stacked,
143}
144
145local stack, enabled, global = { }, false, false
146
147function viewerlayers.enable(value)
148    if value == false or not viewerlayers.supported then
149        if enabled then
150            disableaction("shipouts","attributes.viewerlayers.handler")
151        end
152        enabled = false
153    else
154        if not enabled then
155            enableaction("shipouts","attributes.viewerlayers.handler")
156        end
157        enabled = true
158    end
159end
160
161function viewerlayers.forcesupport(value)
162    viewerlayers.supported = value
163    report_viewerlayers("viewerlayers are %ssupported",value and "" or "not ")
164    viewerlayers.enable(value)
165end
166
167local function register(name,lw) -- if not inimode redefine data[n] in first call
168    if not enabled then
169        viewerlayers.enable(true)
170    end
171    local stamp = f_stamp(name)
172    local n = registered[stamp]
173    if not n then
174        n = #values + 1
175        values[n] = name
176        registered[stamp] = n
177        listwise[n] = lw or false -- lw forces a used
178    end
179    return registered[stamp] -- == n
180end
181
182viewerlayers.register = register
183
184function viewerlayers.setfeatures(hasorder)
185    viewerlayers.hasorder = hasorder
186end
187
188local usestacker = true -- new, experimental
189
190function viewerlayers.start(name)
191    local a
192    if usestacker then
193        a = layerstacker.push(register(name) or unsetvalue)
194    else
195        insert(stack,texgetattribute(a_viewerlayer))
196        a = register(name) or unsetvalue
197    end
198    if global or scopes[name] == v_global then
199        scopes[a] = v_global -- messy but we don't know the attributes yet
200        texsetattribute("global",a_viewerlayer,a)
201    else
202        texsetattribute(a_viewerlayer,a)
203    end
204    -- or macro
205    texsettokenlist("currentviewerlayertoks",name)
206end
207
208function viewerlayers.stop()
209    local a
210    if usestacker then
211        a = layerstacker.pop()
212    else
213        a = remove(stack)
214    end
215    if not a then
216        -- error
217    elseif a >= 0 then
218        if global or scopes[a] == v_global then
219            texsetattribute("global",a_viewerlayer,a)
220        else
221            texsetattribute(a_viewerlayer,a)
222        end
223        texsettokenlist("currentviewerlayertoks",values[a] or "")
224    else
225        if global or scopes[a] == v_global then
226            texsetattribute("global",a_viewerlayer,unsetvalue)
227        else
228            texsetattribute(a_viewerlayer,unsetvalue)
229        end
230        texsettokenlist("currentviewerlayertoks","")
231    end
232end
233
234function viewerlayers.define(settings)
235    local tag = settings.tag
236    if not tag or tag == "" then
237        -- error
238    elseif not scopes[tag] then -- prevent duplicates
239        local title = settings.title
240        if not title or title == "" then
241            settings.title = tag
242        end
243        scopes[tag] = settings.scope or v_local
244        codeinjections.defineviewerlayer(settings)
245    end
246end
247
248function viewerlayers.definedlayoutcomponent(tag)
249    viewerlayers.define {
250        tag       = tag,
251        title     = utilities.strings.nice(tag),
252        visible   = v_start,
253        editable  = v_yes,
254        printable = v_yes,
255    }
256    return register(tag,true) -- true forces a use
257end
258
259function viewerlayers.cleanup()
260    layerstacker.clean()
261    -- todo
262end
263
264implement {
265    name      = "cleanuplayers",
266    actions   = viewerlayers.cleanup
267}
268
269implement {
270    name      = "defineviewerlayer",
271    actions   = viewerlayers.define,
272    arguments = {
273        {
274            { "tag" },
275            { "title" },
276            { "visible" },
277            { "editable" },
278            { "export" },
279            { "printable" },
280            { "scope" },
281        },
282        true
283    }
284}
285
286implement {
287    name      = "definedlayoutcomponent",
288    actions   = { viewerlayers.definedlayoutcomponent, context },
289    arguments = "string"
290}
291
292implement {
293    name      = "startviewerlayer",
294    actions   = viewerlayers.start,
295    arguments = "string",
296}
297
298implement {
299    name      = "stopviewerlayer",
300    actions   = viewerlayers.stop
301}
302
303implement {
304    name      = "registeredviewerlayer",
305    actions   = { register, context },
306    arguments = { "string", true } -- true forces a use
307}
308