• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 11827188491

13 Nov 2024 10:24PM UTC coverage: 33.179% (-27.8%) from 61.013%
11827188491

push

github

alerque
chore(deps): Bump Nix flake dependencies

5947 of 17924 relevant lines covered (33.18%)

1920.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.07
/packages/tableofcontents/init.lua
1
local base = require("packages.base")
2✔
2

3
local package = pl.class(base)
2✔
4
package._name = "tableofcontents"
2✔
5

6
if not SILE.scratch._tableofcontents then
2✔
7
   SILE.scratch._tableofcontents = {}
2✔
8
end
9

10
local toc_used = false
2✔
11

12
function package:moveTocNodes ()
2✔
13
   local node = SILE.scratch.info.thispage.toc
17✔
14
   if node then
17✔
15
      for i = 1, #node do
×
16
         node[i].pageno = self.packages.counters:formatCounter(SILE.scratch.counters.folio)
×
17
         table.insert(SILE.scratch.tableofcontents, node[i])
×
18
      end
19
   end
20
end
21

22
function package.writeToc (_)
2✔
23
   local tocdata = pl.pretty.write(SILE.scratch.tableofcontents)
3✔
24
   local tocfile, err = io.open(pl.path.splitext(SILE.input.filenames[1]) .. ".toc", "w")
4✔
25
   if not tocfile then
2✔
26
      return SU.error(err)
×
27
   end
28
   tocfile:write("return " .. tocdata)
2✔
29
   tocfile:close()
2✔
30

31
   if toc_used and not pl.tablex.deepcompare(SILE.scratch.tableofcontents, SILE.scratch._tableofcontents) then
2✔
32
      SU.msg("Notice: the table of contents has changed, please rerun SILE to update it.")
×
33
   end
34
end
35

36
function package.readToc (_)
2✔
37
   if SILE.scratch._tableofcontents and #SILE.scratch._tableofcontents > 0 then
×
38
      -- already loaded
39
      return SILE.scratch._tableofcontents
×
40
   end
41
   local tocfile_name = (pl.path.splitext(SILE.input.filenames[1]) .. ".toc")
×
42
   local tocfile, _ = io.open(tocfile_name)
×
43
   if not tocfile then
×
44
      return false -- No TOC yet
×
45
   end
46
   local doc = tocfile:read("*all")
×
47
   local toc = assert(load(doc), tocfile_name, "r")()
×
48
   SILE.scratch._tableofcontents = toc
×
49
   return SILE.scratch._tableofcontents
×
50
end
51

52
local function _linkWrapper (dest, func)
53
   if dest and SILE.Commands["pdf:link"] then
×
54
      return function ()
55
         SILE.call("pdf:link", { dest = dest }, func)
×
56
      end
57
   else
58
      return func
×
59
   end
60
end
61

62
-- Flatten a node list into just its string representation.
63
-- (Similar to SU.ast.contentToString(), but allows passing typeset
64
-- objects to functions that need plain strings).
65
local function _nodesToText (nodes)
66
   -- A real interword space width depends on several settings (depending on variable
67
   -- spaces being enabled or not, etc.), and the computation below takes that into
68
   -- account.
69
   local iwspc = SILE.shaper:measureSpace(SILE.font.loadDefaults({}))
×
70
   local iwspcmin = (iwspc.length - iwspc.shrink):tonumber()
×
71

72
   local string = ""
×
73
   for i = 1, #nodes do
×
74
      local node = nodes[i]
×
75
      if node.is_nnode or node.is_unshaped then
×
76
         string = string .. node:toText()
×
77
      elseif node.is_glue or node.is_kern then
×
78
         -- What we want to avoid is "small" glues or kerns to be expanded as full
79
         -- spaces.
80
         -- Comparing them to half of the smallest width of a possibly shrinkable
81
         -- interword space is fairly fragile and empirical: the content could contain
82
         -- font changes, so the comparison is wrong in the general case.
83
         -- It's a simplistic approach. We cannot really be sure what a "space" meant
84
         -- at the point where the kern or glue got absolutized.
85
         if node.width:tonumber() > iwspcmin * 0.5 then
×
86
            string = string .. " "
×
87
         end
88
      elseif not (node.is_zerohbox or node.is_migrating) then
×
89
         -- Here, typically, the main case is an hbox.
90
         -- Even if extracting its content could be possible in some regular cases
91
         -- we cannot take a general decision, as it is a versatile object  and its
92
         -- outputYourself() method could moreover have been redefined to do fancy
93
         -- things. Better warn and skip.
94
         SU.warn("Some content could not be converted to text: " .. node)
×
95
      end
96
   end
97
   -- Trim leading and trailing spaces, and simplify internal spaces.
98
   return pl.stringx.strip(string):gsub("%s%s+", " ")
×
99
end
100

101
if not SILE.scratch.pdf_destination_counter then
2✔
102
   SILE.scratch.pdf_destination_counter = 1
2✔
103
end
104

105
function package:_init ()
2✔
106
   base._init(self)
2✔
107
   if not SILE.scratch.tableofcontents then
2✔
108
      SILE.scratch.tableofcontents = {}
2✔
109
   end
110
   self:loadPackage("infonode")
2✔
111
   self:loadPackage("leaders")
2✔
112
   self.class:registerHook("endpage", self.moveTocNodes)
2✔
113
   self.class:registerHook("finish", self.writeToc)
2✔
114
   self:deprecatedExport("writeToc", self.writeToc)
2✔
115
   self:deprecatedExport("moveTocNodes", self.moveTocNodes)
2✔
116
end
117

118
function package:registerCommands ()
2✔
119
   self:registerCommand("tableofcontents", function (options, _)
4✔
120
      local depth = SU.cast("integer", options.depth or 3)
×
121
      local linking = SU.boolean(options.linking, true)
×
122
      toc_used = true
×
123
      local toc = self:readToc()
×
124
      if toc == false then
×
125
         SILE.call("tableofcontents:notocmessage")
×
126
         return
×
127
      end
128
      SILE.call("tableofcontents:header")
×
129
      for i = 1, #toc do
×
130
         local item = toc[i]
×
131
         if item.level <= depth then
×
132
            SILE.call("tableofcontents:item", {
×
133
               level = item.level,
134
               pageno = item.pageno,
135
               number = item.number,
136
               link = linking and item.link,
137
            }, item.label)
×
138
         end
139
      end
140
      SILE.call("tableofcontents:footer")
×
141
   end)
142

143
   self:registerCommand("tableofcontents:item", function (options, content)
4✔
144
      SILE.settings:temporarily(function ()
×
145
         SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue())
×
146
         SILE.call(
×
147
            "tableofcontents:level" .. options.level .. "item",
×
148
            {},
149
            _linkWrapper(options.link, function ()
×
150
               SILE.call("tableofcontents:level" .. options.level .. "number", {}, function ()
×
151
                  if options.number then
×
152
                     SILE.typesetter:typeset(options.number or "")
×
153
                     SILE.call("kern", { width = "1spc" })
×
154
                  end
155
               end)
156
               SILE.process(content)
×
157
               SILE.call("dotfill")
×
158
               SILE.typesetter:typeset(options.pageno)
×
159
            end)
160
         )
161
      end)
162
   end)
163

164
   self:registerCommand("tocentry", function (options, content)
4✔
165
      local dest
166
      if SILE.Commands["pdf:destination"] then
×
167
         dest = "dest" .. tostring(SILE.scratch.pdf_destination_counter)
×
168
         SILE.call("pdf:destination", { name = dest })
×
169
         SILE.typesetter:pushState()
×
170
         SILE.process(content)
×
171
         local title = _nodesToText(SILE.typesetter.state.nodes)
×
172
         SILE.typesetter:popState()
×
173
         SILE.call("pdf:bookmark", { title = title, dest = dest, level = options.level })
×
174
         SILE.scratch.pdf_destination_counter = SILE.scratch.pdf_destination_counter + 1
×
175
      end
176
      SILE.call("info", {
×
177
         category = "toc",
178
         value = {
×
179
            label = SU.ast.stripContentPos(content),
180
            level = (options.level or 1),
181
            number = options.number,
182
            link = dest,
183
         },
184
      })
185
   end)
186

187
   self:registerCommand("tableofcontents:title", function (_, _)
4✔
188
      SU.deprecated("\\tableofcontents:title", "\\fluent{tableofcontents-title}", "0.13.0", "0.14.0")
×
189
   end, "Deprecated")
2✔
190

191
   self:registerCommand("tableofcontents:notocmessage", function (_, _)
4✔
192
      SILE.call("tableofcontents:headerfont", {}, function ()
×
193
         SILE.call("fluent", {}, { "tableofcontents-not-generated" })
×
194
      end)
195
   end)
196

197
   self:registerCommand("tableofcontents:headerfont", function (_, content)
4✔
198
      SILE.call("font", { size = 24, weight = 800 }, content)
×
199
   end)
200

201
   self:registerCommand("tableofcontents:header", function (_, _)
4✔
202
      SILE.call("par")
×
203
      SILE.call("noindent")
×
204
      SILE.call("tableofcontents:headerfont", {}, function ()
×
205
         SILE.call("fluent", {}, { "tableofcontents-title" })
×
206
      end)
207
      SILE.call("medskip")
×
208
   end)
209

210
   self:registerCommand("tableofcontents:footer", function (_, _) end)
2✔
211

212
   self:registerCommand("tableofcontents:level1item", function (_, content)
4✔
213
      SILE.call("bigskip")
×
214
      SILE.call("noindent")
×
215
      SILE.call("font", { size = 14, weight = 800 }, content)
×
216
      SILE.call("medskip")
×
217
   end)
218

219
   self:registerCommand("tableofcontents:level2item", function (_, content)
4✔
220
      SILE.call("noindent")
×
221
      SILE.call("font", { size = 12 }, content)
×
222
      SILE.call("medskip")
×
223
   end)
224

225
   self:registerCommand("tableofcontents:level3item", function (_, content)
4✔
226
      SILE.call("indent")
×
227
      SILE.call("font", { size = 10 }, content)
×
228
      SILE.call("smallskip")
×
229
   end)
230

231
   self:registerCommand("tableofcontents:level1number", function (_, _) end)
2✔
232

233
   self:registerCommand("tableofcontents:level2number", function (_, _) end)
2✔
234

235
   self:registerCommand("tableofcontents:level3number", function (_, _) end)
2✔
236
end
237

238
package.documentation = [[
239
\begin{document}
240
The \autodoc:package{tableofcontents} package provides tools for class authors to
241
create tables of contents (TOCs). When you are writing sectioning commands such
242
as \autodoc:command[check=false]{\chapter} or \autodoc:command[check=false]{\section}, your classes should call the
243
\autodoc:command{\tocentry[level=<number>, number=<strings>]{<title>}}
244
command to register a table of contents entry.
245
At the end of each page the class will call a hook function (\code{moveTocNodes}) that collates the table of contents entries from that pages and records which page they’re on.
246
At the end of the document another hook function (\code{writeToc}) will write this data to a file.
247
The next time the document is built, any use of the \autodoc:command{\tableofcontents} (typically near the beginning of a document) will be able to read that index data and output the TOC.
248
Because the toc entry and page data is not available until after rendering the document,
249
the TOC will not render until at least the second pass.
250
If by chance rendering the TOC itself changes the document pagination (e.g., the TOC spans more than one page) it will be necessary to run SILE a third time to get accurate page numbers shown in the TOC.
251

252
The \autodoc:command{\tableofcontents} command accepts a \autodoc:parameter{depth} option to
253
control the depth of the content added to the table.
254

255
If the \autodoc:package{pdf} package is loaded before using sectioning commands,
256
then a PDF document outline will be generated.
257
Moreover, entries in the table of contents will be active links to the
258
relevant sections. To disable the latter behavior, pass \autodoc:parameter{linking=false} to
259
the \autodoc:command{\tableofcontents} command.
260

261
Class designers can also style the table of contents by overriding the
262
following commands:
263

264
\begin{itemize}
265
\item{\autodoc:command{\tableofcontents:headerfont}: The font used for the header.}
266
\item{\autodoc:command{\tableofcontents:level1item}, \autodoc:command{\tableofcontents:level2item},
267
      etc.: Styling for entries.}
268
\item{\autodoc:command{\tableofcontents:level1number}, \autodoc:command{\tableofcontents:level2number},
269
      etc.: Deciding what to do with entry section number, if defined: by default, nothing (so they
270
      do not show up in the table of contents).}
271
\end{itemize}
272

273
\end{document}
274
]]
2✔
275

276
return package
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc