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

sile-typesetter / sile / 11124789710

01 Oct 2024 11:57AM UTC coverage: 29.567% (-31.4%) from 60.926%
11124789710

push

github

web-flow
Merge pull request #2105 from Omikhleia/refactor-collated-sort

0 of 10 new or added lines in 1 file covered. (0.0%)

5252 existing lines in 53 files now uncovered.

5048 of 17073 relevant lines covered (29.57%)

1856.13 hits per line

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

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

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

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

10
local toc_used = false
1✔
11

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

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

31
   if toc_used and not pl.tablex.deepcompare(SILE.scratch.tableofcontents, SILE.scratch._tableofcontents) then
1✔
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 (_)
1✔
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.
UNCOV
68
   local iwspc = SILE.shaper:measureSpace(SILE.font.loadDefaults({}))
×
UNCOV
69
   local iwspcmin = (iwspc.length - iwspc.shrink):tonumber()
×
70

UNCOV
71
   local string = ""
×
UNCOV
72
   for i = 1, #nodes do
×
UNCOV
73
      local node = nodes[i]
×
UNCOV
74
      if node.is_nnode or node.is_unshaped then
×
UNCOV
75
         string = string .. node:toText()
×
UNCOV
76
      elseif node.is_glue or node.is_kern then
×
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.
UNCOV
84
         if node.width:tonumber() > iwspcmin * 0.5 then
×
UNCOV
85
            string = string .. " "
×
86
         end
UNCOV
87
      elseif not (node.is_zerohbox or node.is_migrating) then
×
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.
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.
UNCOV
97
   return pl.stringx.strip(string):gsub("%s%s+", " ")
×
98
end
99

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

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

117
function package:registerCommands ()
1✔
118
   self:registerCommand("tableofcontents", function (options, _)
2✔
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
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,
136
            }, item.label)
×
137
         end
138
      end
139
      SILE.call("tableofcontents:footer")
×
140
   end)
141

142
   self:registerCommand("tableofcontents:item", function (options, content)
2✔
143
      SILE.settings:temporarily(function ()
×
144
         SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue())
×
145
         SILE.call(
×
146
            "tableofcontents:level" .. options.level .. "item",
×
147
            {},
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)
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)
2✔
164
      local dest
UNCOV
165
      if SILE.Commands["pdf:destination"] then
×
UNCOV
166
         dest = "dest" .. tostring(SILE.scratch.pdf_destination_counter)
×
UNCOV
167
         SILE.call("pdf:destination", { name = dest })
×
UNCOV
168
         SILE.typesetter:pushState()
×
UNCOV
169
         SILE.process(content)
×
UNCOV
170
         local title = _nodesToText(SILE.typesetter.state.nodes)
×
UNCOV
171
         SILE.typesetter:popState()
×
UNCOV
172
         SILE.call("pdf:bookmark", { title = title, dest = dest, level = options.level })
×
UNCOV
173
         SILE.scratch.pdf_destination_counter = SILE.scratch.pdf_destination_counter + 1
×
174
      end
UNCOV
175
      SILE.call("info", {
×
176
         category = "toc",
UNCOV
177
         value = {
×
178
            label = SU.ast.stripContentPos(content),
179
            level = (options.level or 1),
180
            number = options.number,
181
            link = dest,
182
         },
183
      })
184
   end)
185

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

190
   self:registerCommand("tableofcontents:notocmessage", function (_, _)
2✔
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)
2✔
197
      SILE.call("font", { size = 24, weight = 800 }, content)
×
198
   end)
199

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

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

211
   self:registerCommand("tableofcontents:level1item", function (_, content)
2✔
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)
2✔
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)
2✔
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)
1✔
231

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

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

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