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

sile-typesetter / sile / 5913454052

19 Aug 2023 08:49PM UTC coverage: 54.288% (-20.1%) from 74.359%
5913454052

push

github

web-flow
Merge bb71e7fce into c8a15fb85

8469 of 15600 relevant lines covered (54.29%)

6468.28 hits per line

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

0.0
/packages/tableofcontents/init.lua
1
local base = require("packages.base")
×
2

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

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

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

20
function package.writeToc (_)
×
21
  local tocdata = pl.pretty.write(SILE.scratch.tableofcontents)
×
22
  local tocfile, err = io.open(SILE.masterFilename .. '.toc', "w")
×
23
  if not tocfile then return SU.error(err) end
×
24
  tocfile:write("return " .. tocdata)
×
25
  tocfile:close()
×
26

27
  if not pl.tablex.deepcompare(SILE.scratch.tableofcontents, SILE.scratch._tableofcontents) then
×
28
    io.stderr:write("\n! Warning: table of contents has changed, please rerun SILE to update it.")
×
29
  end
30
end
31

32
function package.readToc (_)
×
33
  if SILE.scratch._tableofcontents and #SILE.scratch._tableofcontents > 0 then
×
34
    -- already loaded
35
    return SILE.scratch._tableofcontents
×
36
  end
37
  local tocfile, _ = io.open(SILE.masterFilename .. '.toc')
×
38
  if not tocfile then
×
39
    return false -- No TOC yet
×
40
  end
41
  local doc = tocfile:read("*all")
×
42
  local toc = assert(load(doc))()
×
43
  SILE.scratch._tableofcontents = toc
×
44
  return SILE.scratch._tableofcontents
×
45
end
46

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

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

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

96
if not SILE.scratch.pdf_destination_counter then
×
97
  SILE.scratch.pdf_destination_counter = 1
×
98
end
99

100
function package:_init ()
×
101
  base._init(self)
×
102
  if not SILE.scratch.tableofcontents then
×
103
    SILE.scratch.tableofcontents = {}
×
104
  end
105
  self:loadPackage("infonode")
×
106
  self:loadPackage("leaders")
×
107
  self.class:registerHook("endpage", self.moveTocNodes)
×
108
  self.class:registerHook("finish", self.writeToc)
×
109
  self:deprecatedExport("writeToc", self.writeToc)
×
110
  self:deprecatedExport("moveTocNodes", self.moveTocNodes)
×
111
end
112

113
function package:registerCommands ()
×
114

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

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

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

182
  self:registerCommand("tableofcontents:title", function (_, _)
×
183
    SU.deprecated("\\tableofcontents:title", "\\fluent{tableofcontents-title}", "0.13.0", "0.14.0")
×
184
  end, "Deprecated")
×
185

186
  self:registerCommand("tableofcontents:notocmessage", function (_, _)
×
187
    SILE.call("tableofcontents:headerfont", {}, function ()
×
188
      SILE.call("fluent", {}, { "tableofcontents-not-generated" })
×
189
    end)
190
  end)
191

192
  self:registerCommand("tableofcontents:headerfont", function (_, content)
×
193
    SILE.call("font", { size = 24, weight = 800 }, content)
×
194
  end)
195

196
  self:registerCommand("tableofcontents:header", function (_, _)
×
197
    SILE.call("par")
×
198
    SILE.call("noindent")
×
199
    SILE.call("tableofcontents:headerfont", {}, function ()
×
200
      SILE.call("fluent", {}, { "tableofcontents-title" })
×
201
    end)
202
    SILE.call("medskip")
×
203
  end)
204

205
  self:registerCommand("tableofcontents:footer", function (_, _) end)
×
206

207
  self:registerCommand("tableofcontents:level1item", function (_, content)
×
208
    SILE.call("bigskip")
×
209
    SILE.call("noindent")
×
210
    SILE.call("font", { size = 14, weight = 800 }, content)
×
211
    SILE.call("medskip")
×
212
  end)
213

214
  self:registerCommand("tableofcontents:level2item", function (_, content)
×
215
    SILE.call("noindent")
×
216
    SILE.call("font", { size = 12 }, content)
×
217
    SILE.call("medskip")
×
218
  end)
219

220
  self:registerCommand("tableofcontents:level3item", function (_, content)
×
221
    SILE.call("indent")
×
222
    SILE.call("font", { size = 10 }, content)
×
223
    SILE.call("smallskip")
×
224
  end)
225

226
  self:registerCommand("tableofcontents:level1number", function (_, _) end)
×
227

228
  self:registerCommand("tableofcontents:level2number", function (_, _) end)
×
229

230
  self:registerCommand("tableofcontents:level3number", function (_, _) end)
×
231

232
end
233

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

248
The \autodoc:command{\tableofcontents} command accepts a \autodoc:parameter{depth} option to
249
control the depth of the content added to the table.
250

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

257
Class designers can also style the table of contents by overriding the
258
following commands:
259

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

269
\end{document}
270
]]
×
271

272
return package
×
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