local base = require("packages.base")
local package = pl.class(base)
package._name = "counters"
SILE.formatCounter = function ()
SU.deprecated("SILE.formatCounter", "class:formatCounter", "0.13.0", "0.15.0")
end
SILE.formatMultilevelCounter = function ()
SU.deprecated("SILE.formatMultilevelCounter", "class:formatMultilevelCounter", "0.13.0", "0.15.0")
end
local function getCounter (_, id)
local counter = SILE.scratch.counters[id]
if not counter then
counter = {
value = 0,
display = "arabic",
format = package.formatCounter,
}
SILE.scratch.counters[id] = counter
elseif type(counter.value) ~= "number" then
SU.error("Counter " .. id .. " is not a single-level counter")
end
return counter
end
local function getMultilevelCounter (_, id)
local counter = SILE.scratch.counters[id]
if not counter then
counter = {
value = { 0 },
display = { "arabic" },
format = package.formatMultilevelCounter,
}
SILE.scratch.counters[id] = counter
elseif type(counter.value) ~= "table" then
SU.error("Counter " .. id .. " is not a multi-level counter")
end
return counter
end
function package:formatCounter (counter)
return SU.formatNumber(counter.value, { system = counter.display })
end
function package:formatMultilevelCounter (counter, options)
options = options or {}
local maxlevel = options.level and SU.min(SU.cast("integer", options.level), #counter.value) or #counter.value
local minlevel = options.minlevel and SU.min(SU.cast("integer", options.minlevel), #counter.value) or 1
local out = {}
if SU.boolean(options.noleadingzeros, false) then
while counter.value[minlevel] == 0 do
minlevel = minlevel + 1
end end
for x = minlevel, maxlevel do
out[x - minlevel + 1] = self:formatCounter({ display = counter.display[x], value = counter.value[x] })
end
return table.concat(out, ".")
end
function package:_init ()
base._init(self)
if not SILE.scratch.counters then
SILE.scratch.counters = {}
end
self:export("getCounter", getCounter)
self:export("getMultilevelCounter", getMultilevelCounter)
self:deprecatedExport("formatCounter", self.formatCounter)
self:deprecatedExport("formatMultilevelCounter", self.formatMultilevelCounter)
end
function package:registerCommands ()
self:registerCommand("increment-counter", function (options, _)
local id = SU.required(options, "id", "increment-counter")
local counter = self.class:getCounter(id)
if options["set-to"] then
SU.deprecated("\\increment-counter[set-to=...]", "\\set-counter[value=...]", "0.14.4", "0.16.0")
counter.value = SU.cast("integer", options["set-to"])
else
counter.value = counter.value + 1
end
if options.display then
counter.display = options.display
end
end, "Increments the counter named by the <id> option")
self:registerCommand(
"set-counter",
function (options, _)
local id = SU.required(options, "id", "set-counter")
local counter = self.class:getCounter(id)
if options.value then
counter.value = SU.cast("integer", options.value)
end
if options.display then
counter.display = options.display
end
end,
"Sets the counter named by the <id> option to <value>; sets its display type (roman/Roman/arabic) to type <display>."
)
self:registerCommand("show-counter", function (options, _)
local id = SU.required(options, "id", "show-counter")
local counter = self.class:getCounter(id)
if options.display then
SU.deprecated("\\show-counter[display=...]", "\\set-counter[display=...]", "0.14.4", "0.16.0")
counter.display = options.display
end
SILE.typesetter:typeset(self:formatCounter(counter))
end, "Outputs the value of counter <id>, optionally displaying it with the <display> format.")
self:registerCommand("increment-multilevel-counter", function (options, _)
local id = SU.required(options, "id", "increment-multilevel-counter")
local counter = self.class:getMultilevelCounter(id)
local currentLevel = #counter.value
local level = SU.cast("integer", options.level or currentLevel)
local reset = SU.boolean(options.reset, true)
if level == currentLevel then
counter.value[level] = counter.value[level] + 1
elseif level > currentLevel then
while level - 1 > currentLevel do
currentLevel = currentLevel + 1
counter.value[currentLevel] = 0
counter.display[currentLevel] = counter.display[currentLevel - 1]
end
currentLevel = currentLevel + 1
counter.value[level] = 1
counter.display[level] = counter.display[currentLevel - 1]
else counter.value[level] = counter.value[level] + 1
while currentLevel > level do
if reset then
counter.value[currentLevel] = nil
counter.display[currentLevel] = nil
end
currentLevel = currentLevel - 1
end
end
if options.display then
counter.display[currentLevel] = options.display
end
end, "Increments the value of the multilevel counter <id> at the given <level> or the current level.")
self:registerCommand(
"set-multilevel-counter",
function (options, _)
local level = SU.cast("integer", SU.required(options, "level", "set-multilevel-counter"))
local id = SU.required(options, "id", "set-multilevel-counter")
local counter = self.class:getMultilevelCounter(id)
local currentLevel = #counter.value
if options.value then
local value = SU.cast("integer", options.value)
if level == currentLevel then
counter.value[level] = value
elseif level > currentLevel then
while level - 1 > currentLevel do
currentLevel = currentLevel + 1
counter.value[currentLevel] = 0
counter.display[currentLevel] = counter.display[currentLevel - 1]
end
currentLevel = currentLevel + 1
counter.value[level] = value
counter.display[level] = counter.display[currentLevel - 1]
else counter.value[level] = value
while currentLevel > level do
counter.value[currentLevel] = nil
counter.display[currentLevel] = nil
currentLevel = currentLevel - 1
end
end
end
if options.display then
if level <= #counter.value then
counter.display[level] = options.display
else
SU.warn("Ignoring attempt to set the display of a multilevel counter beyond its level")
end
end
end,
"Sets the multilevel counter named by the <id> option to <value> at level <level>; optionally sets its display type at that level to <display>."
)
self:registerCommand("show-multilevel-counter", function (options, _)
local id = SU.required(options, "id", "show-multilevel-counter")
local counter = self.class:getMultilevelCounter(id)
if options.display then
SU.deprecated(
"\\show-multilevel-counter[display=...]",
"\\set-multilevel-counter[display=...]",
"0.14.4",
"0.16.0"
)
counter.display[#counter.value] = options.display
end
SILE.typesetter:typeset(self:formatMultilevelCounter(counter, options))
end, "Outputs the value of the multilevel counter <id>.")
end
package.documentation = [[
\begin{document}
Various parts of SILE such as the \autodoc:package{footnotes} package and the sectioning commands keep a counter of things going on: the current footnote number, the chapter number, and so on.
The counters package allows you to set up, increment, and typeset named counters.
It provides the following commands:
\begin{itemize}
\item{\autodoc:command{\set-counter[id=<counter-name>, value=<value>]}: Sets the counter with the specified name to the given value. The command takes an optional \autodoc:parameter{display=<display-type>} parameter to set the display type of the counter (see below).}
\item{\autodoc:command{\increment-counter[id=<counter-name>]}: Increments the counter by one. The command creates the counter if it does not exist and also accepts setting the display type.}
\item{\autodoc:command{\show-counter[id=<counter-name>]}: Typesets the value of the counter according to the counter’s declared display type.}
\end{itemize}
The available built-in display types are:
\begin{itemize}
\item{\code{arabic}, the default}
\item{\code{alpha}, for lower-case alphabetic counting}
\item{\code{Alpha}, for upper-case alphabetic counting}
\item{\code{roman}, for lower-case Roman numerals}
\item{\code{ROMAN}, for upper-case Roman numerals}
\item{\code{greek}, for Greek letters in alphabetical order (not Greek numerals)}
\end{itemize}
The ICU library also provides ways of formatting numbers in global (non-Latin) scripts.
You can use any of the display types in this list: \href{http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/number.xml}.
For example, \autodoc:parameter{display=being} will format your numbers in Bengali digits.
So, for example, the following SILE code:
\begin[type=autodoc:codeblock]{raw}
\set-counter[id=mycounter, value=2]
\show-counter[id=mycounter]
\increment-counter[id=mycounter, display=roman]
\show-counter[id=mycounter]
\end{raw}
produces:
\fullrule
\autodoc:example{
\noindent{}2
\noindent{}iii}
\par
\fullrule
The package also provides multi-level (hierarchical) counters, of the kind used in sectioning
commands:
\begin{itemize}
\item{\autodoc:command{\set-multilevel-counter[id=<counter-name>, level=<level>, value=<value>]}:
Sets the multi-level counter with the specified name to the given value at the given level.
The command also takes an optional \autodoc:parameter{display=<display-type>}, also acting at the given level.}
\item{\autodoc:command{\increment-multilevel-counter[id=<counter-name>]}:
Increments the counter by one at its current (deepest) level.
The command creates the counter if it does not exist.
If given the \autodoc:parameter{level=<level>} parameter, the command increments that level,
clearing any lower level (and filling previous levels with zeros, if they weren’t properly set).
It also accepts setting the display type at the target level.}
\item{\autodoc:command{\show-multilevel-counter[id=<counter-name>]}:
Typesets the value of the multi-level counter according to the counter’s declared display types
at each level. By default, all levels are output; option \autodoc:parameter{level=<level>} may be
used to display the counter up to a given level. Option \autodoc:parameter{noleadingzeros=true}
skips any leading zero (which may happen if a counter is at some level, without previous levels
having been set).}
\end{itemize}
\end{document}
]]
return package