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

sile-typesetter / sile / 9307175333

30 May 2024 06:08PM UTC coverage: 70.644% (-3.5%) from 74.124%
9307175333

push

github

web-flow
Merge b18390e74 into 70ff5c335

1910 of 2592 new or added lines in 108 files covered. (73.69%)

901 existing lines in 52 files now uncovered.

12203 of 17274 relevant lines covered (70.64%)

6112.16 hits per line

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

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

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

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

10
local toc_used = false
17✔
11

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

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

31
   if toc_used and not pl.tablex.deepcompare(SILE.scratch.tableofcontents, SILE.scratch._tableofcontents) then
17✔
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 (_)
17✔
37
   if SILE.scratch._tableofcontents and #SILE.scratch._tableofcontents > 0 then
×
38
      -- already loaded
39
      return SILE.scratch._tableofcontents
×
40
   end
41
   local tocfile, _ = io.open(pl.path.splitext(SILE.input.filenames[1]) .. ".toc")
×
42
   if not tocfile then
×
43
      return false -- No TOC yet
×
44
   end
45
   local doc = tocfile:read("*all")
×
46
   local toc = assert(load(doc))()
×
47
   SILE.scratch._tableofcontents = toc
×
48
   return SILE.scratch._tableofcontents
×
49
end
50

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

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

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

100
if not SILE.scratch.pdf_destination_counter then
17✔
101
   SILE.scratch.pdf_destination_counter = 1
17✔
102
end
103

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

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

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

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

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

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

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

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

209
   self:registerCommand("tableofcontents:footer", function (_, _) end)
17✔
210

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

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

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

230
   self:registerCommand("tableofcontents:level1number", function (_, _) end)
17✔
231

232
   self:registerCommand("tableofcontents:level2number", function (_, _) end)
17✔
233

234
   self:registerCommand("tableofcontents:level3number", function (_, _) end)
17✔
235
end
236

237
package.documentation = [[
238
\begin{document}
239
The \autodoc:package{tableofcontents} package provides tools for class authors to
240
create tables of contents (TOCs). When you are writing sectioning commands such
241
as \autodoc:command[check=false]{\chapter} or \autodoc:command[check=false]{\section}, your classes should call the
242
\autodoc:command{\tocentry[level=<number>, number=<strings>]{<title>}}
243
command to register a table of contents entry.
244
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.
245
At the end of the document another hook function (\code{writeToc}) will write this data to a file.
246
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.
247
Because the toc entry and page data is not available until after rendering the document,
248
the TOC will not render until at least the second pass.
249
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.
250

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

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

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

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

272
\end{document}
273
]]
17✔
274

275
return package
17✔
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