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

sile-typesetter / sile / 6348395828

29 Sep 2023 07:25AM UTC coverage: 71.183% (-3.1%) from 74.301%
6348395828

push

github

alerque
chore(classes): Return more informative error message when failing to finish class

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

11200 of 15734 relevant lines covered (71.18%)

5052.08 hits per line

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

80.49
/classes/base.lua
1
local class = pl.class()
171✔
2
class.type = "class"
171✔
3
class._name = "base"
171✔
4

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

44
class.packages = {}
171✔
45

46
function class:_init (options)
171✔
47
  SILE.scratch.half_initialized_class = self
171✔
48
  if self == options then options = {} end
171✔
49
  SILE.languageSupport.loadLanguage('und') -- preload for unlocalized fallbacks
171✔
50
  self:declareOptions()
171✔
51
  self:registerRawHandlers()
171✔
52
  self:declareSettings()
171✔
53
  self:registerCommands()
171✔
54
  self:setOptions(options)
171✔
55
  self:declareFrames(self.defaultFrameset)
171✔
56
  self:registerPostinit(function (self_)
342✔
57
      if type(self.firstContentFrame) == "string" then
171✔
58
        self_.pageTemplate.firstContentFrame = self_.pageTemplate.frames[self_.firstContentFrame]
171✔
59
      end
60
      local frame = self_:initialFrame()
171✔
61
      SILE.typesetter = SILE.typesetters.base(frame)
342✔
62
      SILE.typesetter:registerPageEndHook(function ()
342✔
63
        SU.debug("frames", function ()
438✔
64
          for _, v in pairs(SILE.frames) do SILE.outputter:debugFrame(v) end
×
65
          return "Drew debug outlines around frames"
×
66
        end)
67
      end)
68
    end)
69
end
70

71
function class:_post_init ()
171✔
72
  self._initialized = true
171✔
73
  for i, func in ipairs(self.deferredInit) do
422✔
74
    func(self)
251✔
75
    self.deferredInit[i] = nil
251✔
76
  end
77
  SILE.scratch.half_initialized_class = nil
171✔
78
end
79

80
function class:setOptions (options)
171✔
81
  options = options or {}
171✔
82
  options.papersize = options.papersize or "a4"
171✔
83
  for option, value in pairs(options) do
360✔
84
    self.options[option] = value
189✔
85
  end
86
end
87

88
function class:declareOption (option, setter)
171✔
89
  rawset(getmetatable(self.options)._opts, option, nil)
524✔
90
  self.options[option] = setter
524✔
91
end
92

93
function class:declareOptions ()
171✔
94
  self:declareOption("class", function (_, name)
342✔
95
    if name then
×
96
      if self._legacy then
×
97
        self._name = name
×
98
      elseif name ~= self._name then
×
99
        SU.error("Cannot change class name after instantiation, derive a new class instead.")
×
100
      end
101
    end
102
    return self._name
×
103
  end)
104
  self:declareOption("papersize", function (_, size)
342✔
105
    if size then
171✔
106
      self.papersize = size
171✔
107
      SILE.documentState.paperSize = SILE.papersize(size)
342✔
108
      SILE.documentState.orgPaperSize = SILE.documentState.paperSize
171✔
109
      SILE.newFrame({
342✔
110
        id = "page",
111
        left = 0,
112
        top = 0,
113
        right = SILE.documentState.paperSize[1],
171✔
114
        bottom = SILE.documentState.paperSize[2]
171✔
115
      })
116
    end
117
    return self.papersize
171✔
118
  end)
119
end
120

121
function class.declareSettings (_)
171✔
122
  SILE.settings:declare({
171✔
123
    parameter = "current.parindent",
124
    type = "glue or nil",
125
    default = nil,
126
    help = "Glue at start of paragraph"
×
127
  })
128
  SILE.settings:declare({
171✔
129
    parameter = "current.hangIndent",
130
    type = "measurement or nil",
131
    default = nil,
132
    help = "Size of hanging indent"
×
133
  })
134
  SILE.settings:declare({
171✔
135
    parameter = "current.hangAfter",
136
    type = "integer or nil",
137
    default = nil,
138
    help = "Number of lines affected by handIndent"
×
139
  })
140
end
141

142
function class:loadPackage (packname, options)
171✔
143
  local pack = require(("packages.%s"):format(packname))
948✔
144
  if type(pack) == "table" and pack.type == "package" then -- new package
948✔
145
    self.packages[pack._name] = pack(options)
1,896✔
146
  else -- legacy package
147
    self:initPackage(pack, options)
×
148
  end
149
end
150

151
function class:initPackage (pack, options)
171✔
152
  SU.deprecated("class:initPackage(options)", "package(options)", "0.14.0", "0.16.0", [[
×
153
  This package appears to be a legacy format package. It returns a table
154
  an expects SILE to guess a bit about what to do. New packages inherit
155
  from the base class and have a constructor function (_init) that
156
  automatically handles setup.]])
×
157
  if type(pack) == "table" then
×
158
    if pack.exports then pl.tablex.update(self, pack.exports) end
×
159
    if type(pack.declareSettings) == "function" then
×
160
      pack.declareSettings(self)
×
161
    end
162
    if type(pack.registerRawHandlers) == "function" then
×
163
      pack.registerRawHandlers(self)
×
164
    end
165
    if type(pack.registerCommands) == "function" then
×
166
      pack.registerCommands(self)
×
167
    end
168
    if type(pack.init) == "function" then
×
169
      self:registerPostinit(pack.init, options)
×
170
    end
171
  end
172
end
173

174
function class:registerLegacyPostinit (func, options)
171✔
175
  if self._initialized then return func(self, options) end
×
176
  table.insert(self.deferredLegacyInit, function (_)
×
177
      func(self, options)
×
178
    end)
179
end
180

181
function class:registerPostinit (func, options)
171✔
182
  if self._initialized then return func(self, options) end
273✔
183
  table.insert(self.deferredInit, function (_)
502✔
184
      func(self, options)
251✔
185
    end)
186
end
187

188
function class:registerHook (category, func)
171✔
189
  for _, func_ in ipairs(self.hooks[category]) do
749✔
190
    if func_ == func then
239✔
191
      return
1✔
192
      --[[ See https://github.com/sile-typesetter/sile/issues/1531
193
      return SU.warn("Attempted to set the same function hook twice, probably unintended, skipping.")
194
      -- If the same function signature is already set a package is probably being
195
      -- re-initialized. Ditch the first instance of the hook so that it runs in
196
      -- the order of last initialization.
197
      self.hooks[category][_] = nil
198
      ]]
199
    end
200
  end
201
  table.insert(self.hooks[category], func)
510✔
202
end
203

204
function class:runHooks (category, options)
171✔
205
  for _, func in ipairs(self.hooks[category]) do
930✔
206
    SU.debug("classhooks", "Running hook from", category, options and "with options " .. #options)
480✔
207
    func(self, options)
480✔
208
  end
209
end
210

211
function class.registerCommand (_, name, func, help, pack)
171✔
212
  SILE.Commands[name] = func
17,640✔
213
  if not pack then
17,640✔
214
    local where = debug.getinfo(2).source
17,625✔
215
    pack = where:match("(%w+).lua")
17,625✔
216
  end
217
  --if not help and not pack:match(".sil") then SU.error("Could not define command '"..name.."' (in package "..pack..") - no help text" ) end
218
  SILE.Help[name] = {
17,640✔
219
    description = help,
17,640✔
220
    where = pack
17,640✔
221
  }
17,640✔
222
end
223

224
function class.registerRawHandler (_, format, callback)
171✔
225
  SILE.rawHandlers[format] = callback
175✔
226
end
227

228
function class:registerRawHandlers ()
171✔
229

230
  self:registerRawHandler("text", function (_, content)
342✔
231
    SILE.settings:temporarily(function()
2✔
232
      SILE.settings:set("typesetter.parseppattern", "\n")
1✔
233
      SILE.settings:set("typesetter.obeyspaces", true)
1✔
234
      SILE.typesetter:typeset(content[1])
1✔
235
    end)
236
  end)
237

238
end
239

240
local function packOptions (options)
241
  local relevant = pl.tablex.copy(options)
146✔
242
  relevant.src = nil
146✔
243
  relevant.format = nil
146✔
244
  relevant.module = nil
146✔
245
  relevant.require = nil
146✔
246
  return relevant
146✔
247
end
248

249
function class:registerCommands ()
171✔
250

251
  local function replaceProcessBy(replacement, tree)
252
    if type(tree) ~= "table" then return tree end
175✔
253
    local ret = pl.tablex.deepcopy(tree)
106✔
254
    if tree.command == "process" then
106✔
255
      return replacement
7✔
256
    else
257
      for i, child in ipairs(tree) do
231✔
258
        ret[i] = replaceProcessBy(replacement, child)
264✔
259
      end
260
      return ret
99✔
261
    end
262
  end
263

264
  self:registerCommand("define", function (options, content)
342✔
265
    SU.required(options, "command", "defining command")
15✔
266
    if type(content) == "function" then
15✔
267
      -- A macro defined as a function can take no argument, so we register
268
      -- it as-is.
269
      self:registerCommand(options["command"], content)
×
270
      return
×
271
    elseif options.command == "process" then
15✔
272
      SU.warn("Did you mean to re-definine the `\\process` macro? That probably won't go well.")
×
273
    end
274
    self:registerCommand(options["command"], function (_, inner_content)
30✔
275
      SU.debug("macros", "Processing macro \\" .. options["command"])
43✔
276
      local macroArg
277
      if type(inner_content) == "function" then
43✔
278
        macroArg = inner_content
2✔
279
      elseif type(inner_content) == "table" then
41✔
280
        macroArg = pl.tablex.copy(inner_content)
78✔
281
        macroArg.command = nil
39✔
282
        macroArg.id = nil
39✔
283
      elseif inner_content == nil then
2✔
284
        macroArg = {}
2✔
285
      else
286
        SU.error("Unhandled content type " .. type(inner_content) .. " passed to macro \\" .. options["command"], true)
×
287
      end
288
      -- Replace every occurrence of \process in `content` (the macro
289
      -- body) with `macroArg`, then have SILE go through the new `content`.
290
      local newContent = replaceProcessBy(macroArg, content)
43✔
291
      SILE.process(newContent)
43✔
292
      SU.debug("macros", "Finished processing \\" .. options["command"])
43✔
293
    end, options.help, SILE.currentlyProcessingFile)
58✔
294
  end, "Define a new macro. \\define[command=example]{ ... \\process }")
186✔
295

296
  -- A utility function that allows SILE.call() to be used as a noop wrapper.
297
  self:registerCommand("noop", function (_, content)
342✔
298
    SILE.process(content)
1✔
299
  end)
300

301
  -- The document (SIL) or sile (XML) command is always the sigular leaf at the
302
  -- top level of our AST. The work you might expect to see happen here is
303
  -- actually handled by SILE.inputter:classInit() before we get here, so these
304
  -- are just pass through functions. Theoretically, this could be a useful
305
  -- point to hook into-especially for included documents.
306
  self:registerCommand("document", function (_, content)
342✔
307
    SILE.process(content)
×
308
  end)
309
  self:registerCommand("sile", function (_, content)
342✔
310
    SILE.process(content)
×
311
  end)
312

313
  self:registerCommand("comment", function (_, _)
342✔
314
  end, "Ignores any text within this command's body.")
171✔
315

316
  self:registerCommand("process", function ()
342✔
317
    SU.error("Encountered unsubstituted \\process.")
×
318
  end, "Within a macro definition, processes the contents of the macro body.")
171✔
319

320
  self:registerCommand("script", function (options, content)
342✔
321
    local packopts = packOptions(options)
38✔
322
    if SU.hasContent(content) then
76✔
323
      return SILE.processString(content[1], options.format or "lua", nil, packopts)
33✔
324
    elseif options.src then
5✔
325
      return SILE.require(options.src)
5✔
326
    else
327
      SU.error("\\script function requires inline content or a src file path")
×
328
      return SILE.processString(content[1], options.format or "lua", nil, packopts)
×
329
    end
330
  end, "Runs lua code. The code may be supplied either inline or using src=...")
171✔
331

332
  self:registerCommand("include", function (options, content)
342✔
333
    local packopts = packOptions(options)
1✔
334
    if SU.hasContent(content) then
2✔
335
      local doc = SU.contentToString(content)
×
336
      return SILE.processString(doc, options.format, nil, packopts)
×
337
    elseif options.src then
1✔
338
      return SILE.processFile(options.src, options.format, packopts)
1✔
339
    else
340
      SU.error("\\include function requires inline content or a src file path")
×
341
    end
342
  end, "Includes a content file for processing.")
171✔
343

344
  self:registerCommand("lua", function (options, content)
342✔
345
    local packopts = packOptions(options)
3✔
346
    if SU.hasContent(content) then
6✔
347
      local doc = SU.contentToString(content)
3✔
348
      return SILE.processString(doc, "lua", nil, packopts)
3✔
349
    elseif options.src then
×
350
      return SILE.processFile(options.src, "lua", packopts)
×
351
    elseif options.require then
×
352
      local module = SU.required(options, "require", "lua")
×
353
      return require(module)
×
354
    else
355
      SU.error("\\lua function requires inline content or a src file path or a require module name")
×
356
    end
357
  end, "Run Lua code. The code may be supplied either inline, using require=... for a Lua module, or using src=... for a file path")
171✔
358

359
  self:registerCommand("sil", function (options, content)
342✔
360
    local packopts = packOptions(options)
×
361
    if SU.hasContent(content) then
×
362
      local doc = SU.contentToString(content)
×
363
      return SILE.processString(doc, "sil")
×
364
    elseif options.src then
×
365
      return SILE.processFile(options.src, "sil", packopts)
×
366
    else
367
      SU.error("\\sil function requires inline content or a src file path")
×
368
    end
369
  end, "Process sil content. The content may be supplied either inline or using src=...")
171✔
370

371
  self:registerCommand("xml", function (options, content)
342✔
372
    local packopts = packOptions(options)
×
373
    if SU.hasContent(content) then
×
374
      local doc = SU.contentToString(content)
×
375
      return SILE.processString(doc, "xml", nil, packopts)
×
376
    elseif options.src then
×
377
      return SILE.processFile(options.src, "xml", packopts)
×
378
    else
379
      SU.error("\\xml function requires inline content or a src file path")
×
380
    end
381
  end, "Process xml content. The content may be supplied either inline or using src=...")
171✔
382

383
  self:registerCommand("use", function (options, content)
342✔
384
    local packopts = packOptions(options)
104✔
385
    if content[1] and string.len(content[1]) > 0 then
104✔
386
      local doc = SU.contentToString(content)
×
387
      SILE.processString(doc, "lua", nil, packopts)
×
388
    else
389
      if options.src then
104✔
390
        SU.warn("Use of 'src' with \\use is discouraged because some of it's path handling\n  will eventually be deprecated. Use 'module' instead when possible.")
×
391
        SILE.processFile(options.src, "lua", packopts)
×
392
      else
393
        local module = SU.required(options, "module", "use")
104✔
394
        SILE.use(module, packopts)
104✔
395
      end
396
    end
397
  end, "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.")
275✔
398

399
  self:registerCommand("raw", function (options, content)
342✔
400
    local rawtype = SU.required(options, "type", "raw")
4✔
401
    local handler = SILE.rawHandlers[rawtype]
4✔
402
    if not handler then SU.error("No inline handler for '"..rawtype.."'") end
4✔
403
    handler(options, content)
4✔
404
  end, "Invoke a raw passthrough handler")
175✔
405

406
  self:registerCommand("pagetemplate", function (options, content)
342✔
407
    SILE.typesetter:pushState()
8✔
408
    SILE.documentState.thisPageTemplate = { frames = {} }
8✔
409
    SILE.process(content)
8✔
410
    SILE.documentState.thisPageTemplate.firstContentFrame = SILE.getFrame(options["first-content-frame"])
16✔
411
    SILE.typesetter:initFrame(SILE.documentState.thisPageTemplate.firstContentFrame)
8✔
412
    SILE.typesetter:popState()
8✔
413
  end, "Defines a new page template for the current page and sets the typesetter to use it.")
179✔
414

415
  self:registerCommand("frame", function (options, _)
342✔
416
    SILE.documentState.thisPageTemplate.frames[options.id] = SILE.newFrame(options)
62✔
417
  end, "Declares (or re-declares) a frame on this page.")
202✔
418

419
  self:registerCommand("penalty", function (options, _)
342✔
420
    if SU.boolean(options.vertical, false) and not SILE.typesetter:vmode() then
577✔
421
      SILE.typesetter:leaveHmode()
4✔
422
    end
423
    if SILE.typesetter:vmode() then
552✔
424
      SILE.typesetter:pushVpenalty({ penalty = tonumber(options.penalty) })
438✔
425
    else
426
      SILE.typesetter:pushPenalty({ penalty = tonumber(options.penalty) })
57✔
427
    end
428
  end, "Inserts a penalty node. Option is penalty= for the size of the penalty.")
447✔
429

430
  self:registerCommand("discretionary", function (options, _)
342✔
431
    local discretionary = SILE.nodefactory.discretionary({})
74✔
432
    if options.prebreak then
74✔
433
      local hbox = SILE.typesetter:makeHbox({ options.prebreak })
74✔
434
      discretionary.prebreak = { hbox }
74✔
435
    end
436
    if options.postbreak then
74✔
437
      local hbox = SILE.typesetter:makeHbox({ options.postbreak })
48✔
438
      discretionary.postbreak = { hbox }
48✔
439
    end
440
    if options.replacement then
74✔
441
      local hbox = SILE.typesetter:makeHbox({ options.replacement })
50✔
442
      discretionary.replacement = { hbox }
50✔
443
    end
444
    table.insert(SILE.typesetter.state.nodes, discretionary)
74✔
445
  end, "Inserts a discretionary node.")
245✔
446

447
  self:registerCommand("glue", function (options, _)
342✔
448
    local width = SU.cast("length", options.width):absolute()
106✔
449
    SILE.typesetter:pushGlue(width)
53✔
450
  end, "Inserts a glue node. The width option denotes the glue dimension.")
224✔
451

452
  self:registerCommand("kern", function (options, _)
342✔
453
    local width = SU.cast("length", options.width):absolute()
150✔
454
    SILE.typesetter:pushHorizontal(SILE.nodefactory.kern(width))
150✔
455
  end, "Inserts a glue node. The width option denotes the glue dimension.")
246✔
456

457
  self:registerCommand("skip", function (options, _)
342✔
458
    options.discardable = SU.boolean(options.discardable, false)
52✔
459
    options.height = SILE.length(options.height):absolute()
78✔
460
    SILE.typesetter:leaveHmode()
26✔
461
    if options.discardable then
26✔
462
      SILE.typesetter:pushVglue(options)
×
463
    else
464
      SILE.typesetter:pushExplicitVglue(options)
26✔
465
    end
466
  end, "Inserts vertical skip. The height options denotes the skip dimension.")
197✔
467

468
  self:registerCommand("par", function (_, _)
342✔
469
    SILE.typesetter:endline()
285✔
470
  end, "Ends the current paragraph.")
456✔
471

472
end
473

474
function class:initialFrame ()
171✔
475
  SILE.documentState.thisPageTemplate = pl.tablex.deepcopy(self.pageTemplate)
450✔
476
  SILE.frames = { page = SILE.frames.page }
225✔
477
  for k, v in pairs(SILE.documentState.thisPageTemplate.frames) do
975✔
478
    SILE.frames[k] = v
750✔
479
  end
480
  if not SILE.documentState.thisPageTemplate.firstContentFrame then
225✔
481
    SILE.documentState.thisPageTemplate.firstContentFrame = SILE.frames[self.firstContentFrame]
×
482
  end
483
  SILE.documentState.thisPageTemplate.firstContentFrame:invalidate()
225✔
484
  return SILE.documentState.thisPageTemplate.firstContentFrame
225✔
485
end
486

487
function class:declareFrame (id, spec)
171✔
488
  spec.id = id
561✔
489
  if spec.solve then
561✔
490
    self.pageTemplate.frames[id] = spec
×
491
  else
492
    self.pageTemplate.frames[id] = SILE.newFrame(spec)
1,122✔
493
  end
494
  --   next = spec.next,
495
  --   left = spec.left and fW(spec.left),
496
  --   right = spec.right and fW(spec.right),
497
  --   top = spec.top and fH(spec.top),
498
  --   bottom = spec.bottom and fH(spec.bottom),
499
  --   height = spec.height and fH(spec.height),
500
  --   width = spec.width and fH(spec.width),
501
  --   id = id
502
  -- })
503
end
504

505
function class:declareFrames (specs)
171✔
506
  if specs then
171✔
507
    for k, v in pairs(specs) do self:declareFrame(k, v) end
1,261✔
508
  end
509
end
510

511
-- WARNING: not called as class method
512
function class.newPar (typesetter)
171✔
513
  local parindent = SILE.settings:get("current.parindent") or SILE.settings:get("document.parindent")
1,632✔
514
  -- See https://github.com/sile-typesetter/sile/issues/1361
515
  -- The parindent *cannot* be pushed non-absolutized, as it may be evaluated
516
  -- outside the (possibly temporary) setting scope where it was used for line
517
  -- breaking.
518
  -- Early absolutization can be problematic sometimes, but here we do not
519
  -- really have the choice.
520
  -- As of problematic cases, consider a parindent that would be defined in a
521
  -- frame-related unit (%lw, %fw, etc.). If a frame break occurs and the next
522
  -- frame has a different width, the parindent won't be re-evaluated in that
523
  -- new frame context. However, defining a parindent in such a unit is quite
524
  -- unlikely. And anyway pushback() has plenty of other issues.
525
  typesetter:pushGlue(parindent:absolute())
1,632✔
526
  SILE.settings:set("current.parindent", nil)
816✔
527
  local hangIndent = SILE.settings:get("current.hangIndent")
816✔
528
  if hangIndent then
816✔
529
    SILE.settings:set("linebreak.hangIndent", hangIndent)
5✔
530
  end
531
  local hangAfter = SILE.settings:get("current.hangAfter")
816✔
532
  if hangAfter then
816✔
533
    SILE.settings:set("linebreak.hangAfter", hangAfter)
5✔
534
  end
535
end
536

537
-- WARNING: not called as class method
538
function class.endPar (typesetter)
171✔
539
  typesetter:pushVglue(SILE.settings:get("document.parskip"))
1,582✔
540
  if SILE.settings:get("current.hangIndent") then
1,582✔
541
    SILE.settings:set("current.hangIndent", nil)
4✔
542
    SILE.settings:set("linebreak.hangIndent", nil)
4✔
543
  end
544
  if SILE.settings:get("current.hangAfter") then
1,582✔
545
    SILE.settings:set("current.hangAfter", nil)
4✔
546
    SILE.settings:set("linebreak.hangAfter", nil)
4✔
547
  end
548
end
549

550
function class:newPage ()
171✔
551
  SILE.outputter:newPage()
54✔
552
  self:runHooks("newpage")
54✔
553
  -- Any other output-routiney things will be done here by inheritors
554
  return self:initialFrame()
54✔
555
end
556

557
function class:endPage ()
171✔
558
  SILE.typesetter.frame:leave(SILE.typesetter)
225✔
559
  self:runHooks("endpage")
225✔
560
  -- I'm trying to call up a new frame here, don't cause a page break in the current one
561
  -- SILE.typesetter:leaveHmode()
562
  -- Any other output-routiney things will be done here by inheritors
563
end
564

565
function class:finish ()
171✔
566
  SILE.inputter:postamble()
171✔
567
  SILE.call("vfill")
171✔
568
  while not SILE.typesetter:isQueueEmpty() do
684✔
569
    SILE.call("supereject")
171✔
570
    SILE.typesetter:leaveHmode(true)
171✔
571
    SILE.typesetter:buildPage()
171✔
572
    if not SILE.typesetter:isQueueEmpty() then
342✔
573
      SILE.typesetter:initNextFrame()
5✔
574
    end
575
  end
576
  SILE.typesetter:runHooks("pageend") -- normally run by the typesetter
171✔
577
  self:endPage()
171✔
578
  if SILE.typesetter and not SILE.typesetter:isQueueEmpty() then
342✔
579
    SU.error("Queues are not empty as expected after ending last page", true)
×
580
  end
581
  SILE.outputter:finish()
171✔
582
  self:runHooks("finish")
171✔
583
end
584

585
return class
171✔
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