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

sile-typesetter / sile / 9304049654

30 May 2024 02:12PM UTC coverage: 60.021% (-14.7%) from 74.707%
9304049654

push

github

web-flow
Merge 1a26b4f22 into a1fd105f8

6743 of 12900 new or added lines in 186 files covered. (52.27%)

347 existing lines in 49 files now uncovered.

10311 of 17179 relevant lines covered (60.02%)

3307.34 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")
3✔
2

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

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

10
local toc_used = false
3✔
11

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

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

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

51
local function _linkWrapper (dest, func)
NEW
52
   if dest and SILE.Commands["pdf:link"] then
×
53
      return function ()
NEW
54
         SILE.call("pdf:link", { dest = dest }, func)
×
55
      end
56
   else
NEW
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.
NEW
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
3✔
101
   SILE.scratch.pdf_destination_counter = 1
3✔
102
end
103

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

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

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

163
   self:registerCommand("tocentry", function (options, content)
6✔
164
      local dest
165
      if SILE.Commands["pdf:destination"] then
2✔
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", {
4✔
176
         category = "toc",
177
         value = {
2✔
178
            label = SU.ast.stripContentPos(content),
4✔
179
            level = (options.level or 1),
2✔
180
            number = options.number,
2✔
181
            link = dest,
2✔
182
         },
2✔
183
      })
184
   end)
185

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

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

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

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

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

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

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

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

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

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

234
   self:registerCommand("tableofcontents:level3number", function (_, _) end)
3✔
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
]]
3✔
274

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