sile 0.15.8

Simon’s Improved Layout Engine
Documentation
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
   -- Option minlevel is undocumented and should perhaps be deprecated: is there a real use case for it?
   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 -- skip leading zeros
   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")
         -- An increment command that does a set is plain weird...
         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)
      -- Option reset=false is undocumented and was previously somewhat broken.
      -- It should perhaps be deprecated: is there a real use case for it?
      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 -- level < currentLevel
         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
               -- e.g. set to x the level 3 of 1.2.3 => 1.2.x
               counter.value[level] = value
            elseif level > currentLevel then
               -- Fill all missing levels in-between, assuming same display format.
               -- e.g. set to x the level 3 of 1 => 1.0.x
               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 -- level < currentLevel
               -- Reset all upper levels
               -- e.g. set to x the level 2 of 1.2.3 => 1.x
               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: \url{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