if not modules then modules = { } end modules ['strc-num'] = { version = 1.001, comment = "companion to strc-num.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local format = string.format local next, type = next, type local min, max = math.min, math.max local texsprint, texcount = tex.sprint, tex.count local trace_counters = false trackers.register("structure.counters", function(v) trace_counters = v end) structure = structure or { } structure.helpers = structure.helpers or { } structure.sections = structure.sections or { } structure.counters = structure.counters or { } structure.documents = structure.documents or { } structure.counters = structure.counters or { } structure.counters.data = structure.counters.data or { } structure.counters.specials = structure.counters.specials or { } local helpers = structure.helpers local sections = structure.sections local counters = structure.counters local documents = structure.documents local variables = interfaces.variables -- state: start stop none reset local counterdata = counters.data local counterranges, tbs = { }, 0 local counterspecials = counters.specials counters.collected = counters.collected or { } counters.tobesaved = counters.tobesaved or { } storage.register("structure/counters/data", structure.counters.data, "structure.counters.data") storage.register("structure/counters/tobesaved", structure.counters.tobesaved, "structure.counters.tobesaved") local collected, tobesaved = counters.collected, counters.tobesaved local function finalizer() local ct = counters.tobesaved for name, cd in next, counterdata do local cs = tobesaved[name] local data = cd.data for i=1,#data do local d = data[i] local r = d.range cs[i][r] = d.number d.range = r + 1 end end end local function initializer() collected, tobesaved = counters.collected, counters.tobesaved end if job then job.register('structure.counters.collected', structure.counters.tobesaved, initializer, finalizer) end local function constructor(t,s,name,i) if s == "last" then local cc = collected[name] t.stop = (cc and cc[i] and cc[i][t.range]) or 0 -- stop is available for diagnostics purposes only if t.offset then return t.stop - t.step else return t.stop end elseif s == "first" then if t.start > 0 then return t.start -- brrr elseif t.offset then return t.start + t.step + 1 else return t.start + 1 end elseif s == "prev" or s == "previous" then return max(t.first,t.number-1) -- todo: step elseif s == "next" then return min(t.last,t.number+1) -- todo: step elseif s == "backward" then if t.number - 1 < t.first then return t.last else return t.previous end elseif s == "forward" then if t.number + 1 > t.last then return t.first else return t.next end elseif s == "subs" then local cc = collected[name] t.subs = (cc and cc[i+1] and cc[i+1][t.range]) or 0 return t.subs else return nil -- was 0, but that is fuzzy in testing for e.g. own end end local enhance = function() for name, cd in next, counterdata do local data = cd.data for i=1,#data do local ci = data[i] setmetatable(ci, { __index = function(t,s) return constructor(t,s,name,i) end }) end end enhance = nil end local function allocate(name,i) local cd = counterdata[name] if not cd then cd = { level = 1, --~ block = "", -- todo numbers = nil, state = variables.start, -- true data = { } } tobesaved[name] = { } counterdata[name] = cd end cd = cd.data local ci = cd[i] if not ci then ci = { number = 0, start = 0, saved = 0, step = 1, range = 1, offset = false, -- via metatable: last, first, and for tracing: stop = 0, } setmetatable(ci, { __index = function(t,s) return constructor(t,s,name,i) end }) cd[i] = ci tobesaved[name][i] = { } else if enhance then enhance() end -- not stored in bytecode end return ci end function counters.record(name,i) return allocate(name,i or 1) end local function savevalue(name,i) if name then local cd = counterdata[name].data[i] local cs = tobesaved[name][i] local cc = collected[name] local cr = cd.range local old = (cc and cc[i] and cc[i][cr]) or 0 cs[cr] = cd.number cd.range = cr + 1 return old else return 0 end end function counters.define(name, start, counter) -- todo: step local d = allocate(name,1) d.start = start if counter ~= "" then d.counter = counter -- only for special purposes, cannot be false end end function counters.trace(name) local cd = counterdata[name] if cd then texsprint(format("[%s:",name)) local data = cd.data for i=1,#data do local d = data[i] texsprint(format(" (%s: %s,%s,%s s:%s r:%s)",i,(d.start or 0),d.number or 0,d.last,d.step or 0,d.range or 0)) end texsprint("]") end end function counters.raw(name) return counterdata[name] end function counters.compact(name,level,onlynumbers) local cd = counterdata[name] --~ print(name,cd) if cd then local data = cd.data local compact = { } for i=1,level or #data do local d = data[i] --~ print(name,i,d.number) if d.number ~= 0 then compact[i] = (onlynumbers and d.number) or d end end --~ print(table.serialize(compact)) return compact end end -- depends on when incremented, before or after (driven by d.offset) function counters.doifelse(name) commands.doifelse(counterdata[name]) end function counters.previous(name,n) texsprint(allocate(name,n).previous) end function counters.next(name,n) texsprint(allocate(name,n).next) end counters.prev = counters.previous function counters.current(name,n) texsprint(allocate(name,n).number) end function counters.first(name,n) texsprint(allocate(name,n).first) end function counters.last(name,n) texsprint(allocate(name,n).last) end function counters.subs(name,n) texsprint(counterdata[name].data[n].subs or 0) end function counters.setvalue(name,tag,value) local cd = counterdata[name] if cd then cd[tag] = value end end function counters.setstate(name,value) -- true/false value = variables[value] if value then counters.setvalue(name,"state",value) end end function counters.setlevel(name,value) counters.setvalue(name,"level",value) end function counters.setoffset(name,value) counters.setvalue(name,"offset",value) end local function synchronize(name,d) local dc = d.counter if dc then if trace_counters then logs.report("counters","setting counter %s with name %s to %s",dc,name,d.number) end tex.setcount("global",dc,d.number) end local cs = counterspecials[name] if cs then if trace_counters then logs.report("counters","invoking special for name %s",name) end cs() end end function counters.reset(name,n) local cd = counterdata[name] if cd then for i=n or 1,#cd.data do local d = cd.data[i] savevalue(name,i) d.number = d.start or 0 d.own = nil synchronize(name,d) end cd.numbers = nil end end function counters.set(name,n,value) local cd = counterdata[name] if cd then local d = allocate(name,n) d.number = value or 0 d.own = nil synchronize(name,d) end end local function check(name,data,start,stop) for i=start or 1,stop or #data do local d = data[i] savevalue(name,i) d.number = d.start or 0 d.own = nil synchronize(name,d) end end function counters.setown(name,n,value) local cd = counterdata[name] if cd then local d = allocate(name,n) d.own = value d.number = (d.number or d.start or 0) + (d.step or 0) local level = cd.level if not level or level == -1 then -- -1 is signal that we reset manually elseif level > 0 then check(name,d,n+1) elseif level == 0 then -- happens elsewhere end synchronize(name,d) end end function counters.restart(name,n,newstart) local cd = counterdata[name] if cd then newstart = tonumber(newstart) if newstart then local d = allocate(name,n) d.start = newstart counters.reset(name,n) end end end function counters.save(name) -- or just number local cd = counterdata[name] if cd then cd.saved = table.copy(cd.data) end end function counters.restore(name) local cd = counterdata[name] if cd and cd.saved then cd.data = cd.saved cd.saved = nil end end function counters.add(name,n,delta) local cd = counterdata[name] if cd and cd.state == variables.start then local data = cd.data local d = allocate(name,n) d.number = (d.number or d.start or 0) + delta*(d.step or 0) local level = cd.level if not level or level == -1 then -- -1 is signal that we reset manually elseif level > 0 then -- within countergroup check(name,data,n+1) elseif level == 0 then -- happens elsewhere end synchronize(name,d) return d.number -- not needed end return 0 end function counters.check(level) -- not used (yet) for name, cd in next, counterdata do -- logs.report("counters","%s %s %s",name,cd.level,level) if cd.level == level then if trace_counters then logs.report("counters","resetting %s at level %s",name,level) end counters.reset(name) end end end function counters.get(name,n,key) local d = allocate(name,n) d = d and d[key] if not d then return 0 elseif type(d) == "function" then return d() else return d end end function counters.value(name,n) -- what to do with own tex.write(counters.get(name,n or 1,'number') or 0) end function counters.converted(name,spec) -- name can be number and reference to storage local cd if type(name) == "number" then cd = specials.retrieve("counter",name) cd = cd and cd.counter else cd = counterdata[name] end if cd then local spec = spec or { } local numbers, ownnumbers = { }, { } local reverse = spec.order == variables.reverse local kind = spec.type or "number" local v_first, v_next, v_previous, v_last = variables.first, variables.next, variables.previous, variables.last local data = cd.data for k=1,#data do local v = data[k] -- somewhat messy, what if subnr? only last must honour kind? local vn if v.own then numbers[k], ownnumbers[k] = v.number, v.own else if kind == v_first then vn = v.first elseif kind == v_next then vn = v.next elseif kind == v_previous then vn = v.prev elseif kind == v_last then vn = v.last else vn = v.number if reverse then local vf = v.first local vl = v.last if vl > 0 then -- vn = vl - vn + 1 + vf vn = vl - vn + vf -- see testbed for test end end end numbers[k], ownnumbers[k] = vn or v.number, nil end end cd.numbers = numbers cd.ownnumbers = ownnumbers sections.typesetnumber(cd,'number',spec) cd.numbers = nil cd.ownnumbers = nil end end -- move to strc-pag.lua function counters.analyse(name,counterspecification) local cd = counterdata[name] -- safeguard if not cd then return false, false, "no counter data" end -- section data local sectiondata = sections.current() if not sectiondata then return cd, false, "not in section" end local references = sectiondata.references if not references then return cd, false, "no references" end local section = references.section if not section then return cd, false, "no section" end sectiondata = jobsections.collected[references.section] if not sectiondata then return cd, false, "no section data" end -- local preferences local no = variables.no if counterspecification and counterspecification.prefix == no then return cd, false, "current spec blocks prefix" end -- stored preferences (not used) if cd.prefix == no then return cd, false, "entry blocks prefix" end -- sectioning -- if sectiondata.prefix == no then -- return false, false, "sectiondata blocks prefix" -- end -- final verdict return cd, sectiondata, "okay" end function counters.prefixedconverted(name,prefixspec,numberspec) local cd, prefixdata, result = counters.analyse(name,prefixspec) if cd then if prefixdata then sections.typesetnumber(prefixdata,"prefix",prefixspec or false,cd or false) end counters.converted(name,numberspec) end end