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

sile-typesetter / sile / 6713098919

31 Oct 2023 10:21PM UTC coverage: 52.831% (-21.8%) from 74.636%
6713098919

push

github

web-flow
Merge d0a2a1ee9 into b185d4972

45 of 45 new or added lines in 3 files covered. (100.0%)

8173 of 15470 relevant lines covered (52.83%)

6562.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
local toc_used = false
×
11

12
function package:moveTocNodes ()
×
13
  local node = SILE.scratch.info.thispage.toc
×
14
  if node then
×
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 (_)
×
23
  local tocdata = pl.pretty.write(SILE.scratch.tableofcontents)
×
24
  local tocfile, err = io.open(pl.path.splitext(SILE.input.filenames[1]) .. '.toc', "w")
×
25
  if not tocfile then return SU.error(err) end
×
26
  tocfile:write("return " .. tocdata)
×
27
  tocfile:close()
×
28

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

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

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

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

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

98
if not SILE.scratch.pdf_destination_counter then
×
99
  SILE.scratch.pdf_destination_counter = 1
×
100
end
101

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

115
function package:registerCommands ()
×
116

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

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

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

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

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

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

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

208
  self:registerCommand("tableofcontents:footer", function (_, _) end)
×
209

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

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

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

229
  self:registerCommand("tableofcontents:level1number", function (_, _) end)
×
230

231
  self:registerCommand("tableofcontents:level2number", function (_, _) end)
×
232

233
  self:registerCommand("tableofcontents:level3number", function (_, _) end)
×
234

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
]]
×
274

275
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

© 2025 Coveralls, Inc