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

sile-typesetter / sile / 9474085178

11 Jun 2024 10:59PM UTC coverage: 69.177% (+13.9%) from 55.259%
9474085178

push

github

alerque
style(classes): Restyle with stylua

1 of 1 new or added line in 1 file covered. (100.0%)

104 existing lines in 9 files now uncovered.

11978 of 17315 relevant lines covered (69.18%)

4307.68 hits per line

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

71.4
/classes/base.lua
1
--- SILE document class interface.
2
-- @interfaces classes
3

4
local class = pl.class()
182✔
5
class.type = "class"
182✔
6
class._name = "base"
182✔
7

8
class._initialized = false
182✔
9
class.deferredLegacyInit = {}
182✔
10
class.deferredInit = {}
182✔
11
class.pageTemplate = { frames = {}, firstContentFrame = nil }
182✔
12
class.defaultFrameset = {}
182✔
13
class.firstContentFrame = "page"
182✔
14
class.options = setmetatable({}, {
364✔
15
   _opts = {},
182✔
16
   __newindex = function (self, key, value)
17
      local opts = getmetatable(self)._opts
932✔
18
      if type(opts[key]) == "function" then
932✔
19
         opts[key](class, value)
750✔
20
      elseif type(value) == "function" then
557✔
21
         opts[key] = value
556✔
22
      elseif type(key) == "number" then
1✔
23
         return
1✔
24
      else
25
         SU.error("Attempted to set an undeclared class option '" .. key .. "'")
×
26
      end
27
   end,
28
   __index = function (self, key)
29
      if key == "super" then
95✔
30
         return nil
×
31
      end
32
      if type(key) == "number" then
95✔
33
         return nil
×
34
      end
35
      local opt = getmetatable(self)._opts[key]
95✔
36
      if type(opt) == "function" then
95✔
37
         return opt(class)
95✔
38
      elseif opt then
×
39
         return opt
×
40
      else
41
         SU.error("Attempted to get an undeclared class option '" .. key .. "'")
×
42
      end
43
   end,
44
})
182✔
45
class.hooks = {
182✔
46
   newpage = {},
182✔
47
   endpage = {},
182✔
48
   finish = {},
182✔
49
}
182✔
50

51
class.packages = {}
182✔
52

53
function class:_init (options)
182✔
54
   SILE.scratch.half_initialized_class = self
92✔
55
   if self == options then
92✔
56
      options = {}
×
57
   end
58
   SILE.languageSupport.loadLanguage("und") -- preload for unlocalized fallbacks
92✔
59
   self:declareOptions()
92✔
60
   self:registerRawHandlers()
92✔
61
   self:declareSettings()
92✔
62
   self:registerCommands()
92✔
63
   self:setOptions(options)
92✔
64
   self:declareFrames(self.defaultFrameset)
92✔
65
   self:registerPostinit(function (self_)
184✔
66
      -- In the event no packages have called \language explicitly or otherwise triggerend the language loader, at this
67
      -- point we'll have a language *setting* but not actually have loaded the language. We put it off as long as we
68
      -- could in case the user changed the default document language and we didn't need to load the system default one,
69
      -- but that time has come at gone at this point. Make sure we've loaded somethnig...
70
      local lang = SILE.settings:get("document.language")
92✔
71
      SILE.languageSupport.loadLanguage(lang)
92✔
72
      if type(self.firstContentFrame) == "string" then
92✔
73
         self_.pageTemplate.firstContentFrame = self_.pageTemplate.frames[self_.firstContentFrame]
92✔
74
      end
75
      local frame = self_:initialFrame()
92✔
76
      SILE.typesetter = SILE.typesetters.base(frame)
184✔
77
      SILE.typesetter:registerPageEndHook(function ()
184✔
78
         SU.debug("frames", function ()
236✔
79
            for _, v in pairs(SILE.frames) do
×
80
               SILE.outputter:debugFrame(v)
×
81
            end
82
            return "Drew debug outlines around frames"
×
83
         end)
84
      end)
85
   end)
86
end
87

88
function class:_post_init ()
182✔
89
   self._initialized = true
92✔
90
   for i, func in ipairs(self.deferredInit) do
219✔
91
      func(self)
127✔
92
      self.deferredInit[i] = nil
127✔
93
   end
94
   SILE.scratch.half_initialized_class = nil
92✔
95
end
96

97
function class:setOptions (options)
182✔
98
   options = options or {}
92✔
99
   -- Classes that add options with dependencies should explicitly handle them, then exempt them from furthur processing.
100
   -- The landscape and crop related options are handled explicitly before papersize, then the "rest" of options that are not interdependent.
101
   self.options.landscape = SU.boolean(options.landscape, false)
184✔
102
   options.landscape = nil
92✔
103
   self.options.papersize = options.papersize or "a4"
92✔
104
   options.papersize = nil
92✔
105
   self.options.bleed = options.bleed or "0"
92✔
106
   options.bleed = nil
92✔
107
   self.options.sheetsize = options.sheetsize or nil
92✔
108
   options.sheetsize = nil
92✔
109
   for option, value in pairs(options) do
100✔
110
      self.options[option] = value
8✔
111
   end
112
end
113

114
function class:declareOption (option, setter)
182✔
115
   rawset(getmetatable(self.options)._opts, option, nil)
556✔
116
   self.options[option] = setter
556✔
117
end
118

119
function class:declareOptions ()
182✔
120
   self:declareOption("class", function (_, name)
184✔
121
      if name then
×
122
         if self._legacy then
×
123
            self._name = name
×
124
         elseif name ~= self._name then
×
125
            SU.error("Cannot change class name after instantiation, derive a new class instead.")
×
126
         end
127
      end
128
      return self._name
×
129
   end)
130
   self:declareOption("landscape", function (_, landscape)
184✔
131
      if landscape then
184✔
132
         self.landscape = landscape
1✔
133
      end
134
      return self.landscape
184✔
135
   end)
136
   self:declareOption("papersize", function (_, size)
184✔
137
      if size then
92✔
138
         self.papersize = size
92✔
139
         SILE.documentState.paperSize = SILE.papersize(size, self.options.landscape)
276✔
140
         SILE.documentState.orgPaperSize = SILE.documentState.paperSize
92✔
141
         SILE.newFrame({
184✔
142
            id = "page",
143
            left = 0,
144
            top = 0,
145
            right = SILE.documentState.paperSize[1],
92✔
146
            bottom = SILE.documentState.paperSize[2],
92✔
147
         })
148
      end
149
      return self.papersize
92✔
150
   end)
151
   self:declareOption("sheetsize", function (_, size)
184✔
152
      if size then
92✔
UNCOV
153
         self.sheetsize = size
×
UNCOV
154
         SILE.documentState.sheetSize = SILE.papersize(size, self.options.landscape)
×
155
         if
UNCOV
156
            SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1]
×
UNCOV
157
            or SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2]
×
158
         then
159
            SU.error("Sheet size shall not be smaller than the paper size")
×
160
         end
UNCOV
161
         if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + SILE.documentState.bleed then
×
162
            SU.debug("frames", "Sheet size width augmented to take page bleed into account")
×
163
            SILE.documentState.sheetSize[1] = SILE.documentState.paperSize[1] + SILE.documentState.bleed
×
164
         end
UNCOV
165
         if SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] + SILE.documentState.bleed then
×
166
            SU.debug("frames", "Sheet size height augmented to take page bleed into account")
×
167
            SILE.documentState.sheetSize[2] = SILE.documentState.paperSize[2] + SILE.documentState.bleed
×
168
         end
169
      else
170
         return self.sheetsize
92✔
171
      end
172
   end)
173
   self:declareOption("bleed", function (_, dimen)
184✔
174
      if dimen then
92✔
175
         self.bleed = dimen
92✔
176
         SILE.documentState.bleed = SU.cast("measurement", dimen):tonumber()
276✔
177
      end
178
      return self.bleed
92✔
179
   end)
180
end
181

182
function class.declareSettings (_)
182✔
183
   SILE.settings:declare({
92✔
184
      parameter = "current.parindent",
185
      type = "glue or nil",
186
      default = nil,
187
      help = "Glue at start of paragraph",
188
   })
189
   SILE.settings:declare({
92✔
190
      parameter = "current.hangIndent",
191
      type = "measurement or nil",
192
      default = nil,
193
      help = "Size of hanging indent",
194
   })
195
   SILE.settings:declare({
92✔
196
      parameter = "current.hangAfter",
197
      type = "integer or nil",
198
      default = nil,
199
      help = "Number of lines affected by handIndent",
200
   })
201
end
202

203
function class:loadPackage (packname, options, reload)
182✔
204
   local pack
205
   -- Allow loading by injecting whole packages as-is, otherwise try to load it with the usual packages path.
206
   if type(packname) == "table" then
610✔
207
      pack, packname = packname, packname._name
123✔
208
   elseif type(packname) == "nil" or packname == "nil" or pl.stringx.strip(packname) == "" then
974✔
209
      SU.error(("Attempted to load package with an invalid packname '%s'"):format(packname))
×
210
   else
211
      pack = require(("packages.%s"):format(packname))
487✔
212
      if pack._name ~= packname then
487✔
213
         SU.error(("Loaded module name '%s' does not match requested name '%s'"):format(pack._name, packname))
×
214
      end
215
   end
216
   SILE.packages[packname] = pack
610✔
217
   if type(pack) == "table" and pack.type == "package" then -- current package api
610✔
218
      if self.packages[packname] then
610✔
219
         -- If the same package name has been loaded before, we might be loading a modified version of the same package or
220
         -- we might be re-loading the same package, or we might just be doubling up work because somebody called us twice.
221
         -- The package itself should take care of the difference between load and reload based on the reload flag here,
222
         -- but in addition to that we also want to avoid creating a new instance. We want to run the intitializer from the
223
         -- (possibly different) new module, but not create a new instance ID and loose any connections it made.
224
         -- To do this we add a create function that returns the current instance. This brings along the _initialized flag
225
         -- and of course anything else already setup and running.
226
         local current_instance = self.packages[packname]
57✔
227
         pack._create = function ()
228
            return current_instance
57✔
229
         end
230
         pack(options, true)
114✔
231
      else
232
         self.packages[packname] = pack(options, reload)
1,106✔
233
      end
234
   else -- legacy package
235
      self:initPackage(pack, options)
×
236
   end
237
end
238

239
function class:reloadPackage (packname, options)
182✔
240
   return self:loadPackage(packname, options, true)
×
241
end
242

243
function class:initPackage (pack, options)
182✔
244
   SU.deprecated(
×
245
      "class:initPackage(options)",
246
      "package(options)",
247
      "0.14.0",
248
      "0.16.0",
249
      [[
×
250
  This package appears to be a legacy format package. It returns a table
251
  and expects SILE to guess about what to do. New packages inherit
252
  from the base class and have a constructor function (_init) that
253
  automatically handles setup.]]
×
254
   )
255
   if type(pack) == "table" then
×
256
      if pack.exports then
×
257
         pl.tablex.update(self, pack.exports)
×
258
      end
259
      if type(pack.declareSettings) == "function" then
×
260
         pack.declareSettings(self)
×
261
      end
262
      if type(pack.registerRawHandlers) == "function" then
×
263
         pack.registerRawHandlers(self)
×
264
      end
265
      if type(pack.registerCommands) == "function" then
×
266
         pack.registerCommands(self)
×
267
      end
268
      if type(pack.init) == "function" then
×
269
         self:registerPostinit(pack.init, options)
×
270
      end
271
   end
272
end
273

274
function class:registerLegacyPostinit (func, options)
182✔
275
   if self._initialized then
×
276
      return func(self, options)
×
277
   end
278
   table.insert(self.deferredLegacyInit, function (_)
×
279
      func(self, options)
×
280
   end)
281
end
282

283
function class:registerPostinit (func, options)
182✔
284
   if self._initialized then
136✔
285
      return func(self, options)
9✔
286
   end
287
   table.insert(self.deferredInit, function (_)
254✔
288
      func(self, options)
127✔
289
   end)
290
end
291

292
function class:registerHook (category, func)
182✔
293
   for _, func_ in ipairs(self.hooks[category]) do
375✔
294
      if func_ == func then
113✔
295
         return
1✔
296
         --[[ See https://github.com/sile-typesetter/sile/issues/1531
297
      return SU.warn("Attempted to set the same function hook twice, probably unintended, skipping.")
298
      -- If the same function signature is already set a package is probably being
299
      -- re-initialized. Ditch the first instance of the hook so that it runs in
300
      -- the order of last initialization.
301
      self.hooks[category][_] = nil
302
      ]]
303
      end
304
   end
305
   table.insert(self.hooks[category], func)
262✔
306
end
307

308
function class:runHooks (category, options)
182✔
309
   for _, func in ipairs(self.hooks[category]) do
493✔
310
      SU.debug("classhooks", "Running hook from", category, options and "with options #" .. #options)
253✔
311
      func(self, options)
253✔
312
   end
313
end
314

315
--- Register a function as a SILE command.
316
-- Takes any Lua function and registers it for use as a SILE command (which will in turn be used to process any content
317
-- nodes identified with the command name.
318
--
319
-- Note that this should only be used to register commands supplied directly by a document class. A similar method is
320
-- available for packages, `packages:registerCommand`.
321
-- @tparam string name Name of cammand to register.
322
-- @tparam function func Callback function to use as command handler.
323
-- @tparam[opt] nil|string help User friendly short usage string for use in error messages, documentation, etc.
324
-- @tparam[opt] nil|string pack Information identifying the module registering the command for use in error and usage
325
-- messages. Usually auto-detected.
326
-- @see SILE.packages:registerCommand
327
function class.registerCommand (_, name, func, help, pack)
182✔
328
   SILE.Commands[name] = func
9,944✔
329
   if not pack then
9,944✔
330
      local where = debug.getinfo(2).source
9,940✔
331
      pack = where:match("(%w+).lua")
9,940✔
332
   end
333
   --if not help and not pack:match(".sil") then SU.error("Could not define command '"..name.."' (in package "..pack..") - no help text" ) end
334
   SILE.Help[name] = {
9,944✔
335
      description = help,
9,944✔
336
      where = pack,
9,944✔
337
   }
9,944✔
338
end
339

340
function class.registerRawHandler (_, format, callback)
182✔
341
   SILE.rawHandlers[format] = callback
95✔
342
end
343

344
function class:registerRawHandlers ()
182✔
345
   self:registerRawHandler("text", function (_, content)
184✔
346
      SILE.settings:temporarily(function ()
2✔
347
         SILE.settings:set("typesetter.parseppattern", "\n")
1✔
348
         SILE.settings:set("typesetter.obeyspaces", true)
1✔
349
         SILE.typesetter:typeset(content[1])
1✔
350
      end)
351
   end)
352
end
353

354
local function packOptions (options)
355
   local relevant = pl.tablex.copy(options)
146✔
356
   relevant.src = nil
146✔
357
   relevant.format = nil
146✔
358
   relevant.module = nil
146✔
359
   relevant.require = nil
146✔
360
   return relevant
146✔
361
end
362

363
function class:registerCommands ()
182✔
364
   local function replaceProcessBy (replacement, tree)
365
      if type(tree) ~= "table" then
34✔
366
         return tree
12✔
367
      end
368
      local ret = pl.tablex.deepcopy(tree)
22✔
369
      if tree.command == "process" then
22✔
370
         return replacement
1✔
371
      else
372
         for i, child in ipairs(tree) do
46✔
373
            ret[i] = replaceProcessBy(replacement, child)
50✔
374
         end
375
         return ret
21✔
376
      end
377
   end
378

379
   self:registerCommand("define", function (options, content)
184✔
380
      SU.required(options, "command", "defining command")
4✔
381
      if type(content) == "function" then
4✔
382
         -- A macro defined as a function can take no argument, so we register
383
         -- it as-is.
384
         self:registerCommand(options["command"], content)
×
385
         return
×
386
      elseif options.command == "process" then
4✔
387
         SU.warn("Did you mean to re-definine the `\\process` macro? That probably won't go well.")
×
388
      end
389
      self:registerCommand(options["command"], function (_, inner_content)
8✔
390
         SU.debug("macros", "Processing macro \\" .. options["command"])
9✔
391
         local macroArg
392
         if type(inner_content) == "function" then
9✔
UNCOV
393
            macroArg = inner_content
×
394
         elseif type(inner_content) == "table" then
9✔
395
            macroArg = pl.tablex.copy(inner_content)
18✔
396
            macroArg.command = nil
9✔
397
            macroArg.id = nil
9✔
UNCOV
398
         elseif inner_content == nil then
×
UNCOV
399
            macroArg = {}
×
400
         else
401
            SU.error(
×
402
               "Unhandled content type " .. type(inner_content) .. " passed to macro \\" .. options["command"],
×
403
               true
404
            )
405
         end
406
         -- Replace every occurrence of \process in `content` (the macro
407
         -- body) with `macroArg`, then have SILE go through the new `content`.
408
         local newContent = replaceProcessBy(macroArg, content)
9✔
409
         SILE.process(newContent)
9✔
410
         SU.debug("macros", "Finished processing \\" .. options["command"])
9✔
411
      end, options.help, SILE.currentlyProcessingFile)
13✔
412
   end, "Define a new macro. \\define[command=example]{ ... \\process }")
96✔
413

414
   -- A utility function that allows SILE.call() to be used as a noop wrapper.
415
   self:registerCommand("noop", function (_, content)
184✔
416
      SILE.process(content)
9✔
417
   end)
418

419
   -- The document (SIL) or sile (XML) command is always the sigular leaf at the
420
   -- top level of our AST. The work you might expect to see happen here is
421
   -- actually handled by SILE.inputter:classInit() before we get here, so these
422
   -- are just pass through functions. Theoretically, this could be a useful
423
   -- point to hook into-especially for included documents.
424
   self:registerCommand("document", function (_, content)
184✔
425
      SILE.process(content)
×
426
   end)
427
   self:registerCommand("sile", function (_, content)
184✔
428
      SILE.process(content)
×
429
   end)
430

431
   self:registerCommand("comment", function (_, _) end, "Ignores any text within this command's body.")
92✔
432

433
   self:registerCommand("process", function ()
184✔
434
      SU.error("Encountered unsubstituted \\process.")
×
435
   end, "Within a macro definition, processes the contents of the macro body.")
92✔
436

437
   self:registerCommand("script", function (options, content)
184✔
438
      local packopts = packOptions(options)
×
439
      local function _deprecated (original, suggested)
440
         SU.deprecated(
×
441
            "\\script",
442
            "\\lua or \\use",
443
            "0.15.0",
444
            "0.16.0",
445
            ([[
×
446
      The \script function has been deprecated. It was overloaded to mean
447
      too many different things and more targeted tools were introduced in
448
      SILE v0.14.0. To load 3rd party modules designed for use with SILE,
449
      replace \script[src=...] with \use[module=...]. To run arbitrary Lua
450
      code inline use \lua{}, optionally with a require= parameter to load
451
      a (non-SILE) Lua module using the Lua module path or src= to load a
452
      file by file path.
453

454
      For this use case consider replacing:
455

456
          %s
457

458
      with:
459

460
          %s
461
      ]]):format(original, suggested)
×
462
         )
463
      end
464
      if SU.ast.hasContent(content) then
×
465
         _deprecated("\\script{...}", "\\lua{...}")
×
466
         return SILE.processString(content[1], options.format or "lua", nil, packopts)
×
467
      elseif options.src then
×
468
         local module = options.src:gsub("%/", ".")
×
469
         local original = (("\\script[src=%s]"):format(options.src))
×
470
         local result = SILE.require(options.src)
×
471
         local suggested = (result._name and "\\use[module=%s]" or "\\lua[require=%s]"):format(module)
×
472
         _deprecated(original, suggested)
×
473
         return result
×
474
      else
475
         SU.error("\\script function requires inline content or a src file path")
×
476
         return SILE.processString(content[1], options.format or "lua", nil, packopts)
×
477
      end
478
   end, "Runs lua code. The code may be supplied either inline or using src=...")
92✔
479

480
   self:registerCommand("include", function (options, content)
184✔
UNCOV
481
      local packopts = packOptions(options)
×
UNCOV
482
      if SU.ast.hasContent(content) then
×
483
         local doc = SU.ast.contentToString(content)
×
484
         return SILE.processString(doc, options.format, nil, packopts)
×
UNCOV
485
      elseif options.src then
×
UNCOV
486
         return SILE.processFile(options.src, options.format, packopts)
×
487
      else
488
         SU.error("\\include function requires inline content or a src file path")
×
489
      end
490
   end, "Includes a content file for processing.")
92✔
491

492
   self:registerCommand(
184✔
493
      "lua",
92✔
494
      function (options, content)
495
         local packopts = packOptions(options)
24✔
496
         if SU.ast.hasContent(content) then
48✔
497
            local doc = SU.ast.contentToString(content)
24✔
498
            return SILE.processString(doc, "lua", nil, packopts)
24✔
499
         elseif options.src then
×
500
            return SILE.processFile(options.src, "lua", packopts)
×
501
         elseif options.require then
×
502
            local module = SU.required(options, "require", "lua")
×
503
            return require(module)
×
504
         else
505
            SU.error("\\lua function requires inline content or a src file path or a require module name")
×
506
         end
507
      end,
508
      "Run Lua code. The code may be supplied either inline, using require=... for a Lua module, or using src=... for a file path"
509
   )
92✔
510

511
   self:registerCommand("sil", function (options, content)
184✔
512
      local packopts = packOptions(options)
×
513
      if SU.ast.hasContent(content) then
×
514
         local doc = SU.ast.contentToString(content)
×
515
         return SILE.processString(doc, "sil")
×
516
      elseif options.src then
×
517
         return SILE.processFile(options.src, "sil", packopts)
×
518
      else
519
         SU.error("\\sil function requires inline content or a src file path")
×
520
      end
521
   end, "Process sil content. The content may be supplied either inline or using src=...")
92✔
522

523
   self:registerCommand("xml", function (options, content)
184✔
524
      local packopts = packOptions(options)
×
525
      if SU.ast.hasContent(content) then
×
526
         local doc = SU.ast.contentToString(content)
×
527
         return SILE.processString(doc, "xml", nil, packopts)
×
528
      elseif options.src then
×
529
         return SILE.processFile(options.src, "xml", packopts)
×
530
      else
531
         SU.error("\\xml function requires inline content or a src file path")
×
532
      end
533
   end, "Process xml content. The content may be supplied either inline or using src=...")
92✔
534

535
   self:registerCommand(
184✔
536
      "use",
92✔
537
      function (options, content)
538
         local packopts = packOptions(options)
122✔
539
         if content[1] and string.len(content[1]) > 0 then
122✔
540
            local doc = SU.ast.contentToString(content)
×
541
            SILE.processString(doc, "lua", nil, packopts)
×
542
         else
543
            if options.src then
122✔
544
               SU.warn(
×
545
                  "Use of 'src' with \\use is discouraged because some of it's path handling\n  will eventually be deprecated. Use 'module' instead when possible."
546
               )
547
               SILE.processFile(options.src, "lua", packopts)
×
548
            else
549
               local module = SU.required(options, "module", "use")
122✔
550
               SILE.use(module, packopts)
122✔
551
            end
552
         end
553
      end,
554
      "Load and initialize a SILE module (can be a package, a shaper, a typesetter, or whatever). Use module=... to specif what to load or include module code inline."
555
   )
92✔
556

557
   self:registerCommand("raw", function (options, content)
184✔
558
      local rawtype = SU.required(options, "type", "raw")
4✔
559
      local handler = SILE.rawHandlers[rawtype]
4✔
560
      if not handler then
4✔
561
         SU.error("No inline handler for '" .. rawtype .. "'")
×
562
      end
563
      handler(options, content)
4✔
564
   end, "Invoke a raw passthrough handler")
96✔
565

566
   self:registerCommand("pagetemplate", function (options, content)
184✔
567
      SILE.typesetter:pushState()
2✔
568
      SILE.documentState.thisPageTemplate = { frames = {} }
2✔
569
      SILE.process(content)
2✔
570
      SILE.documentState.thisPageTemplate.firstContentFrame = SILE.getFrame(options["first-content-frame"])
4✔
571
      SILE.typesetter:initFrame(SILE.documentState.thisPageTemplate.firstContentFrame)
2✔
572
      SILE.typesetter:popState()
2✔
573
   end, "Defines a new page template for the current page and sets the typesetter to use it.")
94✔
574

575
   self:registerCommand("frame", function (options, _)
184✔
576
      SILE.documentState.thisPageTemplate.frames[options.id] = SILE.newFrame(options)
28✔
577
   end, "Declares (or re-declares) a frame on this page.")
106✔
578

579
   self:registerCommand("penalty", function (options, _)
184✔
580
      if SU.boolean(options.vertical, false) and not SILE.typesetter:vmode() then
358✔
581
         SILE.typesetter:leaveHmode()
5✔
582
      end
583
      if SILE.typesetter:vmode() then
332✔
584
         SILE.typesetter:pushVpenalty({ penalty = tonumber(options.penalty) })
268✔
585
      else
586
         SILE.typesetter:pushPenalty({ penalty = tonumber(options.penalty) })
32✔
587
      end
588
   end, "Inserts a penalty node. Option is penalty= for the size of the penalty.")
258✔
589

590
   self:registerCommand("discretionary", function (options, _)
184✔
UNCOV
591
      local discretionary = SILE.types.node.discretionary({})
×
UNCOV
592
      if options.prebreak then
×
UNCOV
593
         local hbox = SILE.typesetter:makeHbox({ options.prebreak })
×
UNCOV
594
         discretionary.prebreak = { hbox }
×
595
      end
UNCOV
596
      if options.postbreak then
×
UNCOV
597
         local hbox = SILE.typesetter:makeHbox({ options.postbreak })
×
UNCOV
598
         discretionary.postbreak = { hbox }
×
599
      end
UNCOV
600
      if options.replacement then
×
UNCOV
601
         local hbox = SILE.typesetter:makeHbox({ options.replacement })
×
UNCOV
602
         discretionary.replacement = { hbox }
×
603
      end
UNCOV
604
      table.insert(SILE.typesetter.state.nodes, discretionary)
×
605
   end, "Inserts a discretionary node.")
92✔
606

607
   self:registerCommand("glue", function (options, _)
184✔
608
      local width = SU.cast("length", options.width):absolute()
42✔
609
      SILE.typesetter:pushGlue(width)
21✔
610
   end, "Inserts a glue node. The width option denotes the glue dimension.")
113✔
611

612
   self:registerCommand("kern", function (options, _)
184✔
613
      local width = SU.cast("length", options.width):absolute()
102✔
614
      SILE.typesetter:pushHorizontal(SILE.types.node.kern(width))
102✔
615
   end, "Inserts a glue node. The width option denotes the glue dimension.")
143✔
616

617
   self:registerCommand("skip", function (options, _)
184✔
618
      options.discardable = SU.boolean(options.discardable, false)
28✔
619
      options.height = SILE.types.length(options.height):absolute()
42✔
620
      SILE.typesetter:leaveHmode()
14✔
621
      if options.discardable then
14✔
622
         SILE.typesetter:pushVglue(options)
×
623
      else
624
         SILE.typesetter:pushExplicitVglue(options)
14✔
625
      end
626
   end, "Inserts vertical skip. The height options denotes the skip dimension.")
106✔
627

628
   self:registerCommand("par", function (_, _)
184✔
629
      SILE.typesetter:endline()
132✔
630
   end, "Ends the current paragraph.")
224✔
631
end
632

633
function class:initialFrame ()
182✔
634
   SILE.documentState.thisPageTemplate = pl.tablex.deepcopy(self.pageTemplate)
240✔
635
   SILE.frames = { page = SILE.frames.page }
120✔
636
   for k, v in pairs(SILE.documentState.thisPageTemplate.frames) do
520✔
637
      SILE.frames[k] = v
400✔
638
   end
639
   if not SILE.documentState.thisPageTemplate.firstContentFrame then
120✔
640
      SILE.documentState.thisPageTemplate.firstContentFrame = SILE.frames[self.firstContentFrame]
×
641
   end
642
   SILE.documentState.thisPageTemplate.firstContentFrame:invalidate()
120✔
643
   return SILE.documentState.thisPageTemplate.firstContentFrame
120✔
644
end
645

646
function class:declareFrame (id, spec)
182✔
647
   spec.id = id
295✔
648
   if spec.solve then
295✔
649
      self.pageTemplate.frames[id] = spec
×
650
   else
651
      self.pageTemplate.frames[id] = SILE.newFrame(spec)
590✔
652
   end
653
   --   next = spec.next,
654
   --   left = spec.left and fW(spec.left),
655
   --   right = spec.right and fW(spec.right),
656
   --   top = spec.top and fH(spec.top),
657
   --   bottom = spec.bottom and fH(spec.bottom),
658
   --   height = spec.height and fH(spec.height),
659
   --   width = spec.width and fH(spec.width),
660
   --   id = id
661
   -- })
662
end
663

664
function class:declareFrames (specs)
182✔
665
   if specs then
92✔
666
      for k, v in pairs(specs) do
382✔
667
         self:declareFrame(k, v)
290✔
668
      end
669
   end
670
end
671

672
-- WARNING: not called as class method
673
function class.newPar (typesetter)
182✔
674
   local parindent = SILE.settings:get("current.parindent") or SILE.settings:get("document.parindent")
1,052✔
675
   -- See https://github.com/sile-typesetter/sile/issues/1361
676
   -- The parindent *cannot* be pushed non-absolutized, as it may be evaluated
677
   -- outside the (possibly temporary) setting scope where it was used for line
678
   -- breaking.
679
   -- Early absolutization can be problematic sometimes, but here we do not
680
   -- really have the choice.
681
   -- As of problematic cases, consider a parindent that would be defined in a
682
   -- frame-related unit (%lw, %fw, etc.). If a frame break occurs and the next
683
   -- frame has a different width, the parindent won't be re-evaluated in that
684
   -- new frame context. However, defining a parindent in such a unit is quite
685
   -- unlikely. And anyway pushback() has plenty of other issues.
686
   typesetter:pushGlue(parindent:absolute())
1,052✔
687
   local hangIndent = SILE.settings:get("current.hangIndent")
526✔
688
   if hangIndent then
526✔
689
      SILE.settings:set("linebreak.hangIndent", hangIndent)
11✔
690
   end
691
   local hangAfter = SILE.settings:get("current.hangAfter")
526✔
692
   if hangAfter then
526✔
693
      SILE.settings:set("linebreak.hangAfter", hangAfter)
11✔
694
   end
695
end
696

697
-- WARNING: not called as class method
698
function class.endPar (typesetter)
182✔
699
   -- If we're already explicitly out of hmode don't do anything special in the way of skips or indents. Assume the user
700
   -- has handled that how they want, e.g. with a skip.
701
   local queue = typesetter.state.outputQueue
650✔
702
   local last_vbox = queue and queue[#queue]
650✔
703
   local last_is_vglue = last_vbox and last_vbox.is_vglue
650✔
704
   local last_is_vpenalty = last_vbox and last_vbox.is_penalty
650✔
705
   if typesetter:vmode() and (last_is_vglue or last_is_vpenalty) then
1,300✔
706
      return
159✔
707
   end
708
   SILE.settings:set("current.parindent", nil)
491✔
709
   typesetter:leaveHmode()
491✔
710
   typesetter:pushVglue(SILE.settings:get("document.parskip"))
982✔
711
end
712

713
function class:newPage ()
182✔
714
   SILE.outputter:newPage()
28✔
715
   self:runHooks("newpage")
28✔
716
   -- Any other output-routiney things will be done here by inheritors
717
   return self:initialFrame()
28✔
718
end
719

720
function class:endPage ()
182✔
721
   SILE.typesetter.frame:leave(SILE.typesetter)
120✔
722
   self:runHooks("endpage")
120✔
723
   -- I'm trying to call up a new frame here, don't cause a page break in the current one
724
   -- SILE.typesetter:leaveHmode()
725
   -- Any other output-routiney things will be done here by inheritors
726
end
727

728
function class:finish ()
182✔
729
   SILE.inputter:postamble()
92✔
730
   SILE.typesetter:endline()
92✔
731
   SILE.call("vfill")
92✔
732
   while not SILE.typesetter:isQueueEmpty() do
368✔
733
      SILE.call("supereject")
92✔
734
      SILE.typesetter:leaveHmode(true)
92✔
735
      SILE.typesetter:buildPage()
92✔
736
      if not SILE.typesetter:isQueueEmpty() then
184✔
737
         SILE.typesetter:initNextFrame()
2✔
738
      end
739
   end
740
   SILE.typesetter:runHooks("pageend") -- normally run by the typesetter
92✔
741
   self:endPage()
92✔
742
   if SILE.typesetter and not SILE.typesetter:isQueueEmpty() then
184✔
743
      SU.error("Queues are not empty as expected after ending last page", true)
×
744
   end
745
   SILE.outputter:finish()
92✔
746
   self:runHooks("finish")
92✔
747
end
748

749
return class
182✔
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