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

sile-typesetter / sile / 8288578143

14 Mar 2024 09:39PM UTC coverage: 64.155% (-10.6%) from 74.718%
8288578143

Pull #1904

github

alerque
chore(core): Fixup ec6ed657 which didn't shim old pack styles properly
Pull Request #1904: Merge develop into master (commit to next release being breaking)

1648 of 2421 new or added lines in 107 files covered. (68.07%)

1843 existing lines in 77 files now uncovered.

10515 of 16390 relevant lines covered (64.15%)

3306.56 hits per line

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

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

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

6
--
7
-- Enumerations and bullet lists for SILE
8
-- Donated to the SILE typesetter - 2021-2022, Didier Willis
9
-- This a trimmed-down version of the feature-richer but more experimental
10
-- "enumitem" package (https://github.com/Omikhleia/omikhleia-sile-packages).
11
-- License: MIT
12
--
13
-- NOTE: Though not described explicitly in the documentation, the package supports
14
-- two nesting techniques:
15
-- The "simple" or compact one:
16
--    \begin{itemize}
17
--       \item{L1.1}
18
--       \begin{itemize}
19
--          \item{L2.1}
20
--       \end{itemize}
21
--    \end{itemize}
22
-- The "alternative" one, which consists in having the nested elements in an item:
23
--    \begin{itemize}
24
--       \item{L1.1
25
--         \begin{itemize}
26
--            \item{L2.1}
27
--         \end{itemize}}
28
--    \end{itemize}
29
-- The latter might be less readable, but is of course more powerful, as other
30
-- contents can be added to the item, as in:
31
--    \begin{itemize}
32
--       \item{L1.1
33
--         \begin{itemize}
34
--            \item{L2.1}
35
--         \end{itemize}%
36
--         This is still in L1.1}
37
--    \end{itemize}
38
-- But personally, for simple lists, I prefer the first "more readable" one.
39
-- Lists from Mardown, obviously, due to their structure, would need the
40
-- second technique.
41
--
42

UNCOV
43
local styles = {
×
UNCOV
44
  enumerate = {
×
UNCOV
45
    { display = "arabic", after = "." },
×
UNCOV
46
    { display = "roman", after = "." },
×
UNCOV
47
    { display = "alpha", after = ")" },
×
UNCOV
48
    { display = "arabic", after = ")" },
×
UNCOV
49
    { display = "roman", after = ")" },
×
UNCOV
50
    { display = "alpha", after = "." },
×
51
  },
UNCOV
52
  itemize = {
×
UNCOV
53
    { bullet = "•" }, -- black bullet
×
UNCOV
54
    { bullet = "â—¦" }, -- circle bullet
×
UNCOV
55
    { bullet = "–" }, -- en-dash
×
UNCOV
56
    { bullet = "•" }, -- black bullet
×
UNCOV
57
    { bullet = "â—¦" }, -- circle bullet
×
UNCOV
58
    { bullet = "–" }, -- en-dash
×
59
  }
60
}
61

62
local trimLeft = function (str)
UNCOV
63
  return str:gsub("^%s*", "")
×
64
end
65

66
local trimRight = function (str)
UNCOV
67
  return str:gsub("%s*$", "")
×
68
end
69

70
local trim = function (str)
UNCOV
71
  return trimRight(trimLeft(str))
×
72
end
73

74
local enforceListType = function (cmd)
NEW
75
  if cmd ~= "enumerate" and cmd ~= "itemize" and cmd ~= "BulletedList" and cmd ~= "OrderedList" then
×
NEW
76
    SU.error("Only items or lists are allowed as content in lists, found '"..cmd.."'")
×
77
  end
78
end
79

UNCOV
80
function package:doItem (options, content)
×
UNCOV
81
  local enumStyle = content._lists_.style
×
UNCOV
82
  local counter = content._lists_.counter
×
UNCOV
83
  local indent = content._lists_.indent
×
84

UNCOV
85
  if not SILE.typesetter:vmode() then
×
86
    SILE.call("par")
×
87
  end
88

UNCOV
89
  local mark = SILE.typesetter:makeHbox(function ()
×
UNCOV
90
    if enumStyle.display then
×
UNCOV
91
      if enumStyle.before then SILE.typesetter:typeset(enumStyle.before) end
×
UNCOV
92
      SILE.typesetter:typeset(self.class.packages.counters:formatCounter({
×
93
        value = counter,
UNCOV
94
        display = enumStyle.display })
×
95
      )
UNCOV
96
      if enumStyle.after then SILE.typesetter:typeset(enumStyle.after) end
×
97
    else
UNCOV
98
      local bullet = options.bullet or enumStyle.bullet
×
UNCOV
99
      SILE.typesetter:typeset(bullet)
×
100
    end
101
  end)
102

103
  local stepback
UNCOV
104
  if enumStyle.display then
×
105
    -- The positioning is quite tentative... LaTeX would right justify the
106
    -- number (at least for roman numerals), i.e.
107
    --   i. Text
108
    --  ii. Text
109
    -- iii. Text.
110
    -- Other Office software do not do that...
UNCOV
111
    local labelIndent = SILE.settings:get("lists.enumerate.labelindent"):absolute()
×
UNCOV
112
    stepback = indent - labelIndent
×
113
  else
114
    -- Center bullets in the indentation space
UNCOV
115
    stepback = indent / 2 + mark.width / 2
×
116
  end
117

UNCOV
118
  SILE.call("kern", { width = -stepback })
×
119
  -- reinsert the mark with modified length
120
  -- using \rebox caused an issue sometimes, not sure why, with the bullets
121
  -- appearing twice in output... but we can avoid it:
122
  -- reboxing an hbox was dumb anyway. We just need to fix its width before
123
  -- reinserting it in the text flow.
NEW
124
  mark.width = SILE.types.length(stepback)
×
UNCOV
125
  SILE.typesetter:pushHbox(mark)
×
UNCOV
126
  SILE.process(content)
×
127
end
128

UNCOV
129
function package.doNestedList (_, listType, options, content)
×
130
  -- depth
UNCOV
131
  local depth = SILE.settings:get("lists.current."..listType..".depth") + 1
×
132

133
  -- styling
UNCOV
134
  local enumStyle = styles[listType][depth]
×
UNCOV
135
  if not enumStyle then SU.error("List nesting is too deep") end
×
136
  -- options may override the default styling
UNCOV
137
  enumStyle = pl.tablex.copy(enumStyle) -- shallow copy for possible overrides
×
UNCOV
138
  if enumStyle.display then
×
UNCOV
139
    if options.before or options.after then
×
140
      -- for before/after, don't mix default style and options
141
      enumStyle.before = options.before or ""
×
142
      enumStyle.after = options.after or ""
×
143
    end
UNCOV
144
    if options.display then enumStyle.display = options.display end
×
145
  else
UNCOV
146
    enumStyle.bullet = options.bullet or enumStyle.bullet
×
147
  end
148

149
  -- indent
NEW
150
  local baseIndent = (depth == 1) and SILE.settings:get("document.parindent").width:absolute() or SILE.types.measurement("0pt")
×
UNCOV
151
  local listIndent = SILE.settings:get("lists."..listType..".leftmargin"):absolute()
×
152

153
  -- processing
UNCOV
154
  if not SILE.typesetter:vmode() then
×
UNCOV
155
    SILE.call("par")
×
156
  end
UNCOV
157
  SILE.settings:temporarily(function ()
×
UNCOV
158
    SILE.settings:set("lists.current."..listType..".depth", depth)
×
NEW
159
    SILE.settings:set("current.parindent", SILE.types.node.glue())
×
NEW
160
    SILE.settings:set("document.parindent", SILE.types.node.glue())
×
UNCOV
161
    SILE.settings:set("document.parskip", SILE.settings:get("lists.parskip"))
×
NEW
162
    local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue()
×
NEW
163
    SILE.settings:set("document.lskip", SILE.types.node.glue(lskip.width + (baseIndent + listIndent)))
×
164

UNCOV
165
    local counter = options.start and (SU.cast("integer", options.start) - 1) or 0
×
UNCOV
166
    for i = 1, #content do
×
NEW
167
      if type(content[i]) == "table" and #content[i] > 0 then
×
NEW
168
        if content[i].command == "item" or content[i].command == "ListItem" then
×
UNCOV
169
          counter = counter + 1
×
170
          -- Enrich the node with internal properties
UNCOV
171
          content[i]._lists_ = {
×
172
            style = enumStyle,
173
            counter = counter,
174
            indent = listIndent,
175
          }
176
        else
UNCOV
177
          enforceListType(content[i].command)
×
178
        end
UNCOV
179
        SILE.process({ content[i] })
×
UNCOV
180
        if not SILE.typesetter:vmode() then
×
UNCOV
181
          SILE.call("par")
×
182
        else
UNCOV
183
          SILE.typesetter:leaveHmode()
×
184
        end
185
         -- Whitespace left around comment nodes is fine too
NEW
186
      elseif type(content[i]) == "table" and #content[i] == 0 then
×
187
         -- ignore whitespace leaking in from in front of indented comments
NEW
188
         assert(true)
×
UNCOV
189
      elseif type(content[i]) == "string" then
×
190
        -- All text nodes are ignored in structure tags, but just warn
191
        -- if there do not just consist in spaces.
UNCOV
192
        local text = trim(content[i])
×
UNCOV
193
        if text ~= "" then SU.warn("Ignored standalone text ("..text..")") end
×
194
      else
195
        SU.error("List structure error")
×
196
      end
197
    end
198
  end)
199

UNCOV
200
  if not SILE.typesetter:vmode() then
×
201
      SILE.call("par")
×
202
  else
UNCOV
203
    SILE.typesetter:leaveHmode()
×
UNCOV
204
    if not((SILE.settings:get("lists.current.itemize.depth")
×
UNCOV
205
        + SILE.settings:get("lists.current.enumerate.depth")) > 0)
×
206
    then
UNCOV
207
      local g = SILE.settings:get("document.parskip").height:absolute() - SILE.settings:get("lists.parskip").height:absolute()
×
UNCOV
208
      SILE.typesetter:pushVglue(g)
×
209
    end
210
  end
211
end
212

UNCOV
213
function package:_init ()
×
UNCOV
214
  base._init(self)
×
UNCOV
215
  self:loadPackage("counters")
×
216
end
217

UNCOV
218
function package.declareSettings (_)
×
219

UNCOV
220
  SILE.settings:declare({
×
221
    parameter = "lists.current.enumerate.depth",
222
    type = "integer",
223
    default = 0,
224
    help = "Current enumerate depth (nesting) - internal"
×
225
  })
226

UNCOV
227
  SILE.settings:declare({
×
228
    parameter = "lists.current.itemize.depth",
229
    type = "integer",
230
    default = 0,
231
    help = "Current itemize depth (nesting) - internal"
×
232
  })
233

UNCOV
234
  SILE.settings:declare({
×
235
    parameter = "lists.enumerate.leftmargin",
236
    type = "measurement",
237
    default = SILE.types.measurement("2em"),
238
    help = "Left margin (indentation) for enumerations"
×
239
  })
240

UNCOV
241
  SILE.settings:declare({
×
242
    parameter = "lists.enumerate.labelindent",
243
    type = "measurement",
244
    default = SILE.types.measurement("0.5em"),
245
    help = "Label indentation for enumerations"
×
246
  })
247

UNCOV
248
  SILE.settings:declare({
×
249
    parameter = "lists.itemize.leftmargin",
250
    type = "measurement",
251
    default = SILE.types.measurement("1.5em"),
252
    help = "Left margin (indentation) for bullet lists (itemize)"
×
253
  })
254

UNCOV
255
  SILE.settings:declare({
×
256
    parameter = "lists.parskip",
257
    type = "vglue",
258
    default = SILE.types.node.vglue("0pt plus 1pt"),
259
    help = "Leading between paragraphs and items in a list"
×
260
  })
261

262
end
263

UNCOV
264
function package:registerCommands ()
×
265

UNCOV
266
  self:registerCommand("enumerate", function (options, content)
×
UNCOV
267
    self:doNestedList("enumerate", options, content)
×
268
  end)
269

UNCOV
270
  self:registerCommand("itemize", function (options, content)
×
UNCOV
271
    self:doNestedList("itemize", options, content)
×
272
  end)
273

UNCOV
274
  self:registerCommand("item", function (options, content)
×
UNCOV
275
    if not content._lists_ then
×
276
      SU.error("The item command shall not be called outside a list")
×
277
    end
UNCOV
278
    self:doItem(options, content)
×
279
  end)
280

281
end
282

283
package.documentation = [[
284
\begin{document}
285
\font:add-fallback[family=Symbola]% HACK Gentium Plus (SILE default font) lacks the circle bullet :(
286
The \autodoc:package{lists} package provides enumerated and itemized (also known as \em{bulleted lists}) which can be nested together.
287

288
\smallskip
289
\noindent
290
\em{Itemized lists}
291
\novbreak
292

293
\indent
294
The \autodoc:environment{itemize} environment initiates a itemized list.
295
Each item, unsurprisingly, is wrapped in an \autodoc:command{\item} command.
296

297
The environment, as a structure or data model, can only contain \code{item} elements or other lists.
298
Any other element causes an error to be reported, and any text content is ignored with a warning.
299

300
\begin{itemize}
301
    \item{Lorem}
302
    \begin{itemize}
303
        \item{Ipsum}
304
        \begin{itemize}
305
            \item{Dolor}
306
        \end{itemize}
307
    \end{itemize}
308
\end{itemize}
309

310
The current implementation supports up to six indentation levels.
311

312
On each level, the indentation is defined by the \autodoc:setting{lists.itemize.leftmargin} setting (defaults to \code{1.5em}) and the bullet is centered in that margin.
313
Note that if your document has a paragraph indent enabled at this point, it is also added to the first list level.
314

315
The package has a default bullet style for each level, but you can explicitly select a bullet symbol of your choice to be used by specifying the options \autodoc:parameter{bullet=<character>} on the \autodoc:environment{itemize} environment.
316
You can also force a specific bullet character to be used on a specific item with \autodoc:command{\item[bullet=<character>]}.
317

318
\smallskip
319
\noindent
320
\em{Enumerated lists}
321
\novbreak
322

323
\indent
324
The \autodoc:environment{enumerate} environment initiates an enumeration.
325
Each item shall, again, be wrapped in an \autodoc:command{\item} command.
326
This environment too is regarded as a structure, so the same rules as above apply.
327

328
The enumeration starts at one, unless you specify the \autodoc:parameter{start=<integer>} option (a numeric value, regardless of the display format).
329

330
\begin{enumerate}
331
    \item{Lorem}
332
    \begin{enumerate}
333
        \item{Ipsum}
334
        \begin{enumerate}
335
            \item{Dolor}
336
        \end{enumerate}
337
    \end{enumerate}
338
\end{enumerate}
339

340
The current implementation supports up to six indentation levels.
341

342
On each level, the indentation is defined by the \autodoc:setting{lists.enumerate.leftmargin} setting (defaults to \code{2em}).
343
Note, again, that if your document has a paragraph indent enabled at this point, it is also added to the first list level.
344

345
% And… ah, at least something less repetitive than a raw list of features.
346
% \em{Quite obviously}, we cannot center the label.
347
% Roman numbers, folks, if any reason is required.
348

349
The \autodoc:setting{lists.enumerate.labelindent} setting specifies the distance between the label and the previous indentation level (defaults to \code{0.5em}).
350
Tune these settings at your convenience depending on your styles.
351
If there is a more general solution to this subtle issue, we accept patches.%
352
\footnote{TeX typesets the enumeration label ragged left. Most word processing software do not.}
353

354
The package has a default number style for each level, but you can explicitly select the display type (format) of the values (as \code{arabic}, \code{roman}, or \code{alpha}), and the text prepended or appended to them, by specifying the options \autodoc:parameter{display=<display>}, \autodoc:parameter{before=<string>}, and \autodoc:parameter{after=<string>} to the \autodoc:environment{enumerate} environment.
355

356
\smallskip
357
\noindent
358
\em{Nesting}
359
\novbreak
360

361
\indent
362
Both environments can be nested.
363
The way they do is best illustrated by an example.
364

365
\begin{enumerate}
366
    \item{Lorem}
367
    \begin{enumerate}
368
        \item{Ipsum}
369
        \begin{itemize}
370
            \item{Dolor}
371
            \begin{enumerate}
372
                \item{Sit amet}
373
                \begin{itemize}
374
                    \item{Consectetur}
375
                \end{itemize}
376
            \end{enumerate}
377
        \end{itemize}
378
    \end{enumerate}
379
\end{enumerate}
380

381
\smallskip
382
\noindent
383
\em{Vertical spaces}
384
\novbreak
385

386
\indent
387
The package tries to ensure a paragraph is enforced before and after a list.
388
In most cases, this implies paragraph skips to be inserted, with the usual \autodoc:setting{document.parskip} glue, whatever value it has at these points in the surrounding context of your document.
389
Between list items, however, the paragraph skip is switched to the value of the \autodoc:setting{lists.parskip} setting.
390

391
\smallskip
392
\noindent
393
\em{Other considerations}
394
\novbreak
395

396
\indent
397
Do not expect these fragile lists to work in any way in centered or ragged-right environments, or with fancy line-breaking features such as hanged or shaped paragraphs.
398
Please be a good typographer. Also, these lists have not yet been tried in right-to-left or vertical writing direction.
399

400
\font:remove-fallback
401
\end{document}
UNCOV
402
]]
×
403

UNCOV
404
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