summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/attr-ini.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/attr-ini.lua')
-rw-r--r--Master/texmf-dist/tex/context/base/attr-ini.lua885
1 files changed, 885 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/attr-ini.lua b/Master/texmf-dist/tex/context/base/attr-ini.lua
new file mode 100644
index 00000000000..c339d467214
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/attr-ini.lua
@@ -0,0 +1,885 @@
+if not modules then modules = { } end modules ['attr-ini'] = {
+ version = 1.001,
+ comment = "companion to attr-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- todo: document this
+
+-- nb: attributes: color etc is much slower than normal (marks + literals) but ...
+-- nb. too many "0 g"s
+
+--
+-- nodes
+--
+
+nodes = nodes or { }
+
+local format, concat = string.format, table.concat
+
+-- This is not the most ideal place, but it will do. Maybe we need to move
+-- attributes to node-att.lua.
+
+do
+
+ -- just for testing
+
+ local reserved = { }
+
+ function nodes.register(n)
+ reserved[#reserved+1] = n
+ end
+
+ function nodes.cleanup_reserved(nofboxes) -- todo
+ local nr, free = #reserved, node.free
+ for i=1,nr do
+ free(reserved[i])
+ end
+ local nl, tb, flush = 0, tex.box, node.flush_list
+ if nofboxes then
+ for i=1,nofboxes do
+ local l = tb[i]
+ if l then
+ -- flush(l)
+ tb[i] = nil
+ nl = nl + 1
+ end
+ end
+ end
+ reserved = { }
+ return nr, nl, nofboxes
+ end
+
+end
+
+do
+
+ local pdfliteral = node.new("whatsit",8) pdfliteral.next, pdfliteral.prev = nil, nil pdfliteral.mode = 1
+ local disc = node.new("disc") disc.next, disc.prev = nil, nil
+ local kern = node.new("kern",1) kern.next, kern.prev = nil, nil
+ local penalty = node.new("penalty") penalty.next, penalty.prev = nil, nil
+ local glue = node.new("glue") glue.next, glue.prev = nil, nil
+ local glue_spec = node.new("glue_spec") glue_spec.next, glue_spec.prev = nil, nil
+ local glyph = node.new("glyph",0) glyph.next, glyph.prev = nil, nil
+
+ nodes.register(pdfliteral)
+ nodes.register(disc)
+ nodes.register(kern)
+ nodes.register(penalty)
+ nodes.register(glue)
+ nodes.register(glue_spec)
+ nodes.register(glyph)
+
+ local copy = node.copy
+
+ function nodes.glyph(fnt,chr)
+ local n = copy(glyph)
+ if fnt then n.font = fnt end
+ if chr then n.char = chr end
+ return n
+ end
+ function nodes.penalty(p)
+ local n = copy(penalty)
+ n.penalty = p
+ return n
+ end
+ function nodes.kern(k)
+ local n = copy(kern)
+ n.kern = k
+ return n
+ end
+ function nodes.glue(width,stretch,shrink)
+ local n = copy(glue)
+ local s = copy(glue_spec)
+ s.width, s.stretch, s.shrink = width, stretch, shrink
+ n.spec = s
+ return n
+ end
+ function nodes.glue_spec(width,stretch,shrink)
+ local s = copy(glue_spec)
+ s.width, s.stretch, s.shrink = width, stretch, shrink
+ return s
+ end
+
+ function nodes.disc()
+ return copy(disc)
+ end
+
+ function nodes.pdfliteral(str)
+ local t = copy(pdfliteral)
+ t.data = str
+ return t
+ end
+
+end
+
+function tex.node_mem_status()
+ -- todo: lpeg
+ local s = status.node_mem_usage
+ local t = { }
+ for n, tag in s:gmatch("(%d+) ([a-z_]+)") do
+ t[tag] = n
+ end
+ return t
+end
+
+--
+-- attributes
+--
+
+attributes = attributes or { }
+
+attributes.names = attributes.names or { }
+attributes.numbers = attributes.numbers or { }
+attributes.list = attributes.list or { }
+
+input.storage.register(false,"attributes/names", attributes.names, "attributes.names")
+input.storage.register(false,"attributes/numbers", attributes.numbers, "attributes.numbers")
+input.storage.register(false,"attributes/list", attributes.list, "attributes.list")
+
+function attributes.define(name,number)
+ attributes.numbers[name], attributes.names[number], attributes.list[number] = number, name, { }
+end
+
+-- We can distinguish between rules and glyphs but it's not worth the trouble. A
+-- first implementation did that and while it saves a bit for glyphs and rules, it
+-- costs more resourses for transparencies. So why bother.
+
+-- namespace for all those features / plural becomes singular
+
+-- i will do the resource stuff later, when we have an interface to pdf (ok, i can
+-- fake it with tokens but it will take some coding
+
+function totokens(str)
+ local t = { }
+--~ for c in string.bytes(str) do
+ for c in str:bytes() do
+ t[#t+1] = { 12, c }
+ end
+ return t
+end
+
+-- temp hack, will be proper driver stuff
+
+backends = backends or { }
+backends.pdf = backends.pdf or { }
+backend = backend or backends.pdf
+
+do
+
+ local pdfliteral, register = nodes.pdfliteral, nodes.register
+
+ function backends.pdf.literal(str)
+ local t = pdfliteral(str)
+ register(t)
+ return t
+ end
+
+end
+
+-- shipouts
+
+shipouts = shipouts or { }
+shipouts.plugins = shipouts.plugins or { }
+
+do
+
+ local pairs = pairs -- in theory faster
+
+ local hlist, vlist = node.id('hlist'), node.id('vlist')
+
+ local has_attribute = node.has_attribute
+
+ nodes.trigger = nodes.trigger or false
+ nodes.triggering = nodes.triggering or false
+
+ -- we used to do the main processor loop here and call processor for each node
+ -- but eventually this was too much a slow down (1 sec on 23 for 120 pages mk)
+ -- so that we moved looping to the processor itself; this may lead to a bit of
+ -- duplicate code once that we have more state handlers
+
+ local starttiming, stoptiming = input.starttiming, input.stoptiming
+
+ local function process_attributes(head,plugins)
+ if head then -- is already tested
+ starttiming(attributes)
+ local done, used = false, { }
+ local trigger, numbers = nodes.trigger, attributes.numbers
+ for name, plugin in pairs(plugins) do
+ local attribute = numbers[name]
+ if attribute then
+ local namespace = plugin.namespace
+ if namespace.enabled then
+ local initializer = plugin.initializer
+ local processor = plugin.processor
+ local finalizer = plugin.finalizer
+ local resolver = plugin.resolver
+ if initializer then
+ initializer(namespace,attribute,head)
+ end
+ if processor then
+ local inheritance = (resolver and resolver()) or -1
+ local ok -- = false
+ head, ok = processor(namespace,attribute,head,inheritance)
+ done = done or ok
+ end
+ if finalizer then -- no need when not ok
+ local ok -- = false
+ head, ok, used[attribute] = finalizer(namespace,attribute,head)
+ done = done or ok
+ end
+ end
+ else
+ texio.write_nl(format("undefined attribute %s",name))
+ end
+ end
+ if done then
+ for name, plugin in pairs(plugins) do
+ local attribute = numbers[name]
+ if used[attribute] then
+ local namespace = plugin.namespace
+ if namespace.enabled then
+ local flusher = plugin.flusher
+ if flusher then
+ local h, d = flusher(namespace,attribute,head,used[attribute])
+ head = h
+ end
+ end
+ end
+ end
+ end
+ stoptiming(attributes)
+ return head, done
+ else
+ return head, false
+ end
+ end
+
+ nodes.process_attributes = process_attributes
+
+ --~ glyph = 746876
+ --~ glue = 376096
+ --~ hlist = 152284
+ --~ disc = 47224
+ --~ kern = 41504
+ --~ penalty = 31964
+ --~ whatsit = 29048
+ --~ vlist = 20136
+ --~ rule = 13292
+ --~ mark = 4304
+ --~ math = 1072
+
+ local disc, mark, free = node.id('disc'), node.id('mark'), node.free
+
+ local function cleanup_page(head) -- rough
+ local prev, start = nil, head
+ while start do
+ local id, nx = start.id, start.next
+ if id == disc or id == mark then
+ if prev then
+ prev.next = nx
+ end
+ if start == head then
+ head = nx
+ end
+ local tmp = start
+ start = nx
+ free(tmp)
+ elseif id == hlist or id == vlist then
+ local sl = start.list
+ if sl then
+ start.list = cleanup_page(sl)
+ end
+ prev, start = start, nx
+ else
+ prev, start = start, nx
+ end
+ end
+ return head
+ end
+
+ nodes.cleanup_page = cleanup_page
+
+ nodes.cleanup_page_first = false
+
+ function nodes.process_page(head)
+ if nodes.cleanup_page_first then
+ head = cleanup_page(head)
+ end
+ return process_attributes(head,shipouts.plugins)
+ end
+
+end
+
+--
+-- generic handlers
+--
+
+states = { }
+
+do
+
+ local glyph, rule, whatsit, hlist, vlist = node.id('glyph'), node.id('rule'), node.id('whatsit'), node.id('hlist'), node.id('vlist')
+
+ local has_attribute, copy = node.has_attribute, node.copy
+
+ local current, used, done = 0, { }, false
+
+ function states.initialize(what, attribute, stack)
+ current, used, done = 0, { }, false
+ end
+
+ local function insert(n,stack,previous,head) -- there is a helper, we need previous because we are not slided
+ if n then
+ n = copy(n)
+ n.next = stack
+ if previous then
+ previous.next = n
+ else
+ head = n
+ end
+ previous = n
+ end
+ return stack, head
+ end
+
+ function states.finalize(namespace,attribute,head) -- is this one ok?
+ if current > 0 then
+ local nn = namespace.none
+ if nn then
+ local id = head.id
+ if id == hlist or id == vlist then
+ local list = head.list
+ if list then
+ local _, h = insert(nn,list,nil,list)
+ head.list = h
+ end
+ else
+ stack, head = insert(nn,head,nil,head)
+ end
+ return head, true, true
+ end
+ end
+ return head, false, false
+ end
+
+ local function process(namespace,attribute,head,inheritance,default) -- one attribute
+ local trigger = namespace.triggering and nodes.triggering and nodes.trigger
+ local stack, previous, done = head, nil, false
+ local nsdata, nsreviver, nsnone = namespace.data, namespace.reviver, namespace.none
+ while stack do
+ local id = stack.id
+ if id == glyph or id == whatsit or id == rule then -- or disc
+ local c = has_attribute(stack,attribute)
+ if c then
+ if default and c == inheritance then
+ if current ~= default then
+ local data = nsdata[default] or nsreviver(default)
+ stack, head = insert(data,stack,previous,head)
+ current, done, used[default] = default, true, true
+ end
+ elseif current ~= c then
+ local data = nsdata[c] or nsreviver(c)
+ stack, head = insert(data,stack,previous,head)
+ current, done, used[c] = c, true, true
+ end
+ elseif default and inheritance then
+ if current ~= default then
+ local data = nsdata[default] or nsreviver(default)
+ stack, head = insert(data,stack,previous,head)
+ current, done, used[default] = default, true, true
+ end
+ elseif current > 0 then
+ stack, head = insert(nsnone,stack,previous,head)
+ current, done, used[0] = 0, true, true
+ end
+ elseif id == hlist or id == vlist then
+ local content = stack.list
+ if content then
+ local ok = false
+ if trigger and has_attribute(stack,trigger) then
+ local outer = has_attribute(stack,attribute)
+ if outer ~= inheritance then
+ stack.list, ok = process(namespace,attribute,content,inheritance,outer)
+ else
+ stack.list, ok = process(namespace,attribute,content,inheritance,default)
+ end
+ else
+ stack.list, ok = process(namespace,attribute,content,inheritance,default)
+ end
+ done = done or ok
+ end
+ end
+ previous = stack
+ stack = stack.next
+ end
+ return head, done
+ end
+
+ states.process = process
+
+ -- we can force a selector, e.g. document wide color spaces, saves a little
+
+ local function selective(namespace,attribute,head,inheritance,default) -- two attributes
+ local trigger = namespace.triggering and nodes.triggering and nodes.trigger
+ local stack, previous, done = head, nil, false
+ local nsselector, nsforced, nsselector = namespace.default, namespace.forced, namespace.selector
+ local nsdata, nsreviver, nsnone = namespace.data, namespace.reviver, namespace.none
+ while stack do
+ local id = stack.id
+ if id == glyph or id == whatsit or id == rule then -- or disc
+ -- todo: maybe track two states, also selector
+ local c = has_attribute(stack,attribute)
+ if c then
+ if default and c == inheritance then
+ if current ~= default then
+ local data = nsdata[default] or nsreviver(default)
+ stack, head = insert(data[nsforced or has_attribute(stack,nsselector) or nsselector],stack,previous,head)
+ current, done, used[default] = default, true, true
+ end
+ elseif current ~= c then
+ local data = nsdata[c] or nsreviver(c)
+ stack, head = insert(data[nsforced or has_attribute(stack,nsselector) or nsselector],stack,previous,head)
+ current, done, used[c] = c, true, true
+ end
+ elseif default and inheritance then
+ if current ~= default then
+ local data = nsdata[default] or nsreviver(default)
+ stack, head = insert(data[nsforced or has_attribute(stack,nsselector) or nsselector],stack,previous,head)
+ current, done, used[default] = default, true, true
+ end
+ elseif current > 0 then
+ stack, head = insert(nsnone,stack,previous,head)
+ current, done, used[0] = 0, true, true
+ end
+ elseif id == hlist or id == vlist then
+ local content = stack.list
+ if content then
+ local ok = false
+ if trigger and has_attribute(stack,trigger) then
+ local outer = has_attribute(stack,attribute)
+ if outer ~= inheritance then
+ stack.list, ok = selective(namespace,attribute,content,inheritance,outer)
+ else
+ stack.list, ok = selective(namespace,attribute,content,inheritance,default)
+ end
+ else
+ stack.list, ok = selective(namespace,attribute,content,inheritance,default)
+ end
+ done = done or ok
+ end
+ end
+ previous = stack
+ stack = stack.next
+ end
+ return head, done
+ end
+
+ states.selective = selective
+
+end
+
+states = states or { }
+states.collected = states.collected or { }
+
+input.storage.register(false,"states/collected", states.collected, "states.collected")
+
+function states.collect(str)
+ local collected = states.collected
+ collected[#collected+1] = str
+end
+
+function states.flush()
+ local collected = states.collected
+ if #collected > 0 then
+ for i=1,#collected do
+ tex.sprint(tex.ctxcatcodes,collected[i]) -- we're in context mode anyway
+ end
+ states.collected = { }
+ end
+end
+
+function states.check()
+ texio.write_nl(concat(states.collected,"\n"))
+end
+
+--
+-- colors
+--
+
+-- we can also collapse the two attributes: n, n+1, n+2 and then
+-- at the tex end add 0, 1, 2, but this is not faster and less
+-- flexible (since sometimes we freeze color attribute values at
+-- the lua end of the game
+
+-- we also need to store the colorvalues because we need then in mp
+
+colors = colors or { }
+colors.data = colors.data or { }
+colors.values = colors.values or { }
+colors.registered = colors.registered or { }
+colors.enabled = true
+colors.weightgray = true
+colors.attribute = 0
+colors.selector = 0
+colors.default = 1
+colors.main = nil
+colors.triggering = true
+
+-- This is a compromis between speed and simplicity. We used to store the
+-- values and data in one array, which made in neccessary to store the
+-- converters that need node constructor into strings and evaluate them
+-- at runtime (after reading from storage). Think of:
+--
+-- colors.strings = colors.strings or { }
+--
+-- if environment.initex then
+-- colors.strings[color] = "return colors." .. colorspace .. "(" .. concat({...},",") .. ")"
+-- end
+--
+-- input.storage.register(true,"colors/data", colors.strings, "colors.data") -- evaluated
+--
+-- We assume that only processcolors are defined in the format.
+
+input.storage.register(false,"colors/values", colors.values, "colors.values")
+input.storage.register(false,"colors/registered", colors.registered, "colors.registered")
+
+colors.stamps = {
+ rgb = "r:%s:%s:%s",
+ cmyk = "c:%s:%s:%s:%s",
+ gray = "s:%s",
+ spot = "p:%s:%s:%s:%s"
+}
+
+colors.models = {
+ all = 1,
+ gray = 2,
+ rgb = 3,
+ cmyk = 4,
+}
+
+colors.model = "all"
+
+do
+
+ local min = math.min
+ local max = math.max
+
+ local function rgbdata(r,g,b) -- dodo: backends.pdf.rgbdata
+ return backends.pdf.literal(format("%s %s %s rg %s %s %s RG",r,g,b,r,g,b))
+ end
+
+ local function cmykdata(c,m,y,k)
+ return backends.pdf.literal(format("%s %s %s %s k %s %s %s %s K",c,m,y,k,c,m,y,k))
+ end
+
+ local function graydata(s)
+ return backends.pdf.literal(format("%s g %s G",s,s))
+ end
+
+ local function spotdata(n,f,d,p)
+ if type(p) == "string" then
+ p = p:gsub(","," ") -- brr misuse of spot
+ end
+ return backends.pdf.literal(format("/%s cs /%s CS %s SCN %s scn",n,n,p,p))
+ end
+
+ local function rgbtocmyk(r,g,b) -- we could reduce
+ return 1-r, 1-g, 1-b, 0
+ end
+
+ local function cmyktorgb(c,m,y,k)
+ return 1.0 - min(1.0,c+k), 1.0 - min(1.0,m+k), 1.0 - min(1.0,y+k)
+ end
+
+ local function rgbtogray(r,g,b)
+ if colors.weightgray then
+ return .30*r+.59*g+.11*b
+ else
+ return r/3+g/3+b/3
+ end
+ end
+
+ local function cmyktogray(c,m,y,k)
+ return rgbtogray(cmyktorgb(c,m,y,k))
+ end
+
+ colors.rgbtocmyk = rgbtocmyk
+ colors.rgbtogray = rgbtogray
+ colors.cmyktorgb = cmyktorgb
+ colors.cmyktogray = cmyktogray
+
+ -- we can share some *data by using s, rgb and cmyk hashes, but
+ -- normally the amount of colors is not that large; storing the
+ -- components costs a bit of extra runtime, but we expect to gain
+ -- some back because we have them at hand; the number indicates the
+ -- default color space
+
+ function colors.gray(s)
+ return { 2, s, s, s, s, 0, 0, 0, 1-s }
+ end
+
+ function colors.rgb(r,g,b)
+ local s = rgbtogray(r,g,b)
+ local c, m, y, k = rgbtocmyk(r,g,b)
+ return { 3, s, r, g, b, c, m, y, k }
+ end
+
+ function colors.cmyk(c,m,y,k)
+ local s = cmyktogray(c,m,y,k)
+ local r, g, b = cmyktorgb(c,m,y,k)
+ return { 4, s, r, g, b, c, m, y, k }
+ end
+
+ function colors.spot(parent,f,d,p)
+--~ if type(p) == "string" and p:find(",") then
+--~ -- use converted replacement (combination color)
+--~ else
+--~ -- todo: map gray, rgb, cmyk onto fraction*parent
+--~ end
+ return { 5, .5, .5, .5, .5, 0, 0, 0, .5, parent, f, d, p }
+ end
+
+ function colors.reviver(n)
+ local d = colors.data[n]
+ if not d then
+ local v = colors.values[n]
+ if not v then
+ local gray = graydata(0)
+ d = { gray, gray, gray, gray }
+ logs.report("attributes",format("unable to revive color %s",n or "?"))
+ else
+ local kind, gray, rgb, cmyk = v[1], graydata(v[2]), rgbdata(v[3],v[4],v[5]), cmykdata(v[6],v[7],v[8],v[9])
+ if kind == 2 then
+ d = { gray, gray, gray, gray }
+ elseif kind == 3 then
+ d = { rgb, gray, rgb, cmyk }
+ elseif kind == 4 then
+ d = { cmyk, gray, rgb, cmyk }
+ elseif kind == 5 then
+ local spot = spotdata(v[10],v[11],v[12],v[13])
+ d = { spot, gray, rgb, cmyk }
+ end
+ end
+ colors.data[n] = d
+ end
+ return d
+ end
+
+ function colors.filter(n)
+ return concat(colors.data[n],":",5)
+ end
+
+ colors.none = graydata(0)
+
+end
+
+function colors.setmodel(attribute,name)
+ colors.model = name
+ colors.selector = attributes.numbers[attribute]
+ colors.default = colors.models[name] or 1
+ return colors.default
+end
+
+function colors.register(attribute, name, colorspace, ...) -- passing 9 vars is faster
+ local stamp = format(colors.stamps[colorspace], ...)
+ local color = colors.registered[stamp]
+ if not color then
+ color = #colors.values+1
+ colors.values[color] = colors[colorspace](...)
+ colors.registered[stamp] = color
+ colors.reviver(color)
+ end
+ if name then
+ attributes.list[attributes.numbers[attribute]][name] = color -- not grouped, so only global colors
+ end
+ return colors.registered[stamp]
+end
+
+function colors.value(id)
+ return colors.values[id]
+end
+
+shipouts.plugins.color = {
+ namespace = colors,
+ initializer = states.initialize,
+ finalizer = states.finalize,
+ processor = states.selective,
+ resolver = function(...) return colors.main end,
+}
+
+-- transparencies
+
+-- for the moment we manage transparencies in the pdf driver because
+-- first we need a nice interface to some pdf things
+
+transparencies = transparencies or { }
+transparencies.registered = transparencies.registered or { }
+transparencies.data = transparencies.data or { }
+transparencies.values = transparencies.values or { }
+transparencies.enabled = false
+transparencies.template = "%s:%s"
+transparencies.triggering = true
+
+input.storage.register(false, "transparencies/registered", transparencies.registered, "transparencies.registered")
+input.storage.register(false, "transparencies/values", transparencies.values, "transparencies.values")
+
+function transparencies.reference(n)
+ return backends.pdf.literal(format("/Tr%s gs",n))
+end
+
+function transparencies.register(name,a,t)
+ local stamp = format(transparencies.template,a,t)
+ local n = transparencies.registered[stamp]
+ if not n then
+ n = #transparencies.data+1
+ transparencies.data[n] = transparencies.reference(n)
+ transparencies.values[n] = { a, t }
+ transparencies.registered[stamp] = n
+ states.collect(format("\\presetPDFtransparencybynumber{%s}{%s}{%s}",n,a,t)) -- too many, but experimental anyway
+ end
+ return transparencies.registered[stamp]
+end
+
+function transparencies.reviver(n)
+ local d = transparencies.data[n]
+ if not d then
+ local v = transparencies.values[n]
+ if not v then
+ d = transparencies.reference(0)
+ logs.report("attributes",format("unable to revive transparency %s",n or "?"))
+ else
+ d = transparencies.reference(n)
+ states.collect(format("\\presetPDFtransparencybynumber{%s}{%s}{%s}",n,v[1],v[2]))
+ end
+ transparencies.data[n] = d
+ end
+ return d
+end
+
+-- check if there is an identity
+
+--~ transparencies.none = transparencies.reference(transparencies.register(nil,1,1))
+
+transparencies.none = transparencies.reference(0) -- for the moment the pdf backend does this
+
+function transparencies.value(id)
+ return transparencies.values[id]
+end
+
+shipouts.plugins.transparency = {
+ namespace = transparencies,
+ initializer = states.initialize,
+ finalizer = states.finalize ,
+ processor = states.process ,
+}
+
+--- overprint / knockout
+
+overprints = overprints or { }
+overprints.data = overprints.data or { }
+overprints.enabled = false
+
+overprints.data[1] = backends.pdf.literal(format("/GSoverprint gs"))
+overprints.data[2] = backends.pdf.literal(format("/GSknockout gs"))
+
+overprints.none = overprints.data[1]
+
+overprints.registered = {
+ overprint = 1,
+ knockout = 2,
+}
+
+function overprints.register(stamp)
+-- states.collect(tex.sprint(tex.ctxcatcodes,"\\initializePDFoverprint")) -- to be testd
+ return overprints.registered[stamp] or overprints.registered.overprint
+end
+
+shipouts.plugins.overprint = {
+ namespace = overprints,
+ initializer = states.initialize,
+ finalizer = states.finalize ,
+ processor = states.process ,
+}
+
+--- negative / positive
+
+negatives = netatives or { }
+negatives.data = negatives.data or { }
+negatives.enabled = false
+
+negatives.data[1] = backends.pdf.literal(format("/GSpositive gs"))
+negatives.data[2] = backends.pdf.literal(format("/GSnegative gs"))
+
+negatives.none = negatives.data[1]
+
+negatives.registered = {
+ positive = 1,
+ negative = 2,
+}
+
+function negatives.register(stamp)
+-- states.collect(tex.sprint(tex.ctxcatcodes,"\\initializePDFnegative")) -- to be testd
+ return negatives.registered[stamp] or negatives.registered.positive
+end
+
+shipouts.plugins.negative = {
+ namespace = negatives,
+ initializer = states.initialize,
+ finalizer = states.finalize,
+ processor = states.process,
+}
+
+-- effects
+
+effects = effects or { }
+effects.data = effects.data or { }
+effects.registered = effects.registered or { }
+effects.enabled = false
+effects.stamp = "%s:%s:%s"
+
+input.storage.register(false, "effects/registered", effects.registered, "effects.registered")
+input.storage.register(false, "effects/data", effects.data, "effects.data")
+
+function effects.register(effect,stretch,rulethickness)
+ local stamp = format(effects.stamp,effect,stretch,rulethickness)
+ local n = effects.registered[stamp]
+ if not n then
+ n = #effects.data+1
+ effects.data[n] = effects.reference(effect,stretch,rulethickness)
+ effects.registered[stamp] = n
+ -- states.collect("") -- nothing
+ end
+ return effects.registered[stamp]
+end
+
+backends.pdf.effects = {
+ normal = 0,
+ inner = 0,
+ outer = 1,
+ both = 2,
+ hidden = 3,
+}
+
+function effects.reference(effect,stretch,rulethickness)
+ -- always, no zero test (removed)
+ rulethickness = number.dimenfactors["bp"]*rulethickness
+ effect = backends.pdf.effects[effect] or backends.pdf.effects['normal']
+ return backends.pdf.literal(format("%s Tc %s w %s Tr",stretch,rulethickness,effect)) -- watch order
+end
+
+effects.none = effects.reference(0,0,0) -- faster: backends.pdf.literal("0 Tc 0 w 0 Tr")
+
+shipouts.plugins.effect = {
+ namespace = effects,
+ initializer = states.initialize,
+ finalizer = states.finalize,
+ processor = states.process,
+}
+
+-- layers
+
+--~ /OC /somename BDC
+--~ EMC