diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/attr-ini.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/attr-ini.lua | 885 |
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 |