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

sile-typesetter / sile / 9304049654

30 May 2024 02:12PM UTC coverage: 60.021% (-14.7%) from 74.707%
9304049654

push

github

web-flow
Merge 1a26b4f22 into a1fd105f8

6743 of 12900 new or added lines in 186 files covered. (52.27%)

347 existing lines in 49 files now uncovered.

10311 of 17179 relevant lines covered (60.02%)

3307.34 hits per line

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

73.53
/types/node.lua
1
--- SILE node type.
2
-- @types node
3

4
local nodetypes = {}
35✔
5

6
-- This infinity needs to be smaller than an actual infinity but bigger than the infinite stretch
7
-- added by the typesetter. See https://github.com/sile-typesetter/sile/issues/227
8
local infinity = SILE.types.measurement(1e13)
70✔
9

10
local function _maxnode (nodes, dim)
11
   local dims = SU.map(function (node)
8,110✔
12
      -- TODO there is a bug here because we shouldn't need to cast to lengths,
13
      -- somebody is setting a height as a number value (test in Lua 5.1)
14
      -- return node[dim]
15
      return SU.cast("length", node[dim])
10,048✔
16
   end, nodes)
4,055✔
17
   return SU.max(SILE.types.length(0), pl.utils.unpack(dims))
12,165✔
18
end
19

20
local _dims = pl.Set({ "width", "height", "depth" })
70✔
21

22
nodetypes.box = pl.class()
70✔
23
nodetypes.box.type = "special"
35✔
24

25
nodetypes.box.height = nil
35✔
26
nodetypes.box.depth = nil
35✔
27
nodetypes.box.width = nil
35✔
28
nodetypes.box.misfit = false
35✔
29
nodetypes.box.explicit = false
35✔
30
nodetypes.box.discardable = false
35✔
31
nodetypes.box.value = nil
35✔
32
nodetypes.box._default_length = "width"
35✔
33

34
function nodetypes.box:_init (spec)
70✔
35
   if
36
      type(spec) == "string"
7,929✔
37
      or type(spec) == "number"
7,661✔
38
      or SU.type(spec) == "measurement"
14,758✔
39
      or SU.type(spec) == "length"
14,660✔
40
   then
41
      self[self._default_length] = SU.cast("length", spec)
2,702✔
42
   elseif SU.type(spec) == "table" then
13,156✔
43
      if spec._tospec then
4,316✔
NEW
44
         spec = spec:_tospec()
×
45
      end
46
      for k, v in pairs(spec) do
22,873✔
47
         self[k] = _dims[k] and SU.cast("length", v) or v
24,055✔
48
      end
49
   elseif type(spec) ~= "nil" and SU.type(spec) ~= self.type then
2,262✔
NEW
50
      SU.error("Unimplemented, creating " .. self.type .. " node from " .. SU.type(spec), 1)
×
51
   end
52
   for dim in pairs(_dims) do
31,716✔
53
      if not self[dim] then
24,249✔
54
         self[dim] = SILE.types.length()
33,876✔
55
      end
56
   end
57
   self["is_" .. self.type] = true
7,929✔
58
   self.is_box = self.is_hbox or self.is_vbox or self.is_zerohbox or self.is_alternative or self.is_nnode
8,699✔
59
   self.is_zero = self.is_zerohbox or self.is_zerovglue
8,237✔
60
   if self.is_migrating then
8,083✔
61
      self.is_hbox, self.is_box = true, true
2✔
62
   end
63
end
64

65
-- De-init instances by shallow copying properties and removing meta table
66
function nodetypes.box:_tospec ()
70✔
67
   return pl.tablex.copy(self)
136✔
68
end
69

70
function nodetypes.box:tostring ()
70✔
NEW
71
   return self:__tostring()
×
72
end
73

74
function nodetypes.box:__tostring ()
70✔
NEW
75
   return self.type
×
76
end
77

78
function nodetypes.box.__concat (a, b)
70✔
NEW
79
   return tostring(a) .. tostring(b)
×
80
end
81

82
function nodetypes.box:absolute ()
70✔
83
   local clone = nodetypes[self.type](self:_tospec())
272✔
84
   for dim in pairs(_dims) do
544✔
85
      clone[dim] = self[dim]:absolute()
816✔
86
   end
87
   if self.nodes then
136✔
NEW
88
      clone.nodes = pl.tablex.map_named_method("absolute", self.nodes)
×
89
   end
90
   return clone
136✔
91
end
92

93
function nodetypes.box:lineContribution ()
70✔
94
   -- Regardless of the orientations, "width" is always in the
95
   -- writingDirection, and "height" is always in the "pageDirection"
96
   return self.misfit and self.height or self.width
4,887✔
97
end
98

99
function nodetypes.box:outputYourself ()
70✔
NEW
100
   SU.error(self.type .. " with no output routine")
×
101
end
102

103
function nodetypes.box:toText ()
70✔
NEW
104
   return self.type
×
105
end
106

107
function nodetypes.box:isBox ()
70✔
NEW
108
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
109
   return self.type == "hbox"
×
NEW
110
      or self.type == "zerohbox"
×
NEW
111
      or self.type == "alternative"
×
NEW
112
      or self.type == "nnode"
×
NEW
113
      or self.type == "vbox"
×
114
end
115

116
function nodetypes.box:isNnode ()
70✔
NEW
117
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
118
   return self.type == "nnode"
×
119
end
120

121
function nodetypes.box:isGlue ()
70✔
NEW
122
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
123
   return self.type == "glue"
×
124
end
125

126
function nodetypes.box:isVglue ()
70✔
NEW
127
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
128
   return self.type == "vglue"
×
129
end
130

131
function nodetypes.box:isZero ()
70✔
NEW
132
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
133
   return self.type == "zerohbox" or self.type == "zerovglue"
×
134
end
135

136
function nodetypes.box:isUnshaped ()
70✔
NEW
137
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
138
   return self.type == "unshaped"
×
139
end
140

141
function nodetypes.box:isAlternative ()
70✔
NEW
142
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
143
   return self.type == "alternative"
×
144
end
145

146
function nodetypes.box:isVbox ()
70✔
NEW
147
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
148
   return self.type == "vbox"
×
149
end
150

151
function nodetypes.box:isInsertion ()
70✔
NEW
152
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
153
   return self.type == "insertion"
×
154
end
155

156
function nodetypes.box:isMigrating ()
70✔
NEW
157
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
158
   return self.migrating
×
159
end
160

161
function nodetypes.box:isPenalty ()
70✔
NEW
162
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
163
   return self.type == "penalty"
×
164
end
165

166
function nodetypes.box:isDiscretionary ()
70✔
NEW
167
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
168
   return self.type == "discretionary"
×
169
end
170

171
function nodetypes.box:isKern ()
70✔
NEW
172
   SU.warn("Deprecated function, please use boolean is_<type> property to check types", true)
×
NEW
173
   return self.type == "kern"
×
174
end
175

176
nodetypes.hbox = pl.class(nodetypes.box)
70✔
177
nodetypes.hbox.type = "hbox"
35✔
178

179
function nodetypes.hbox:_init (spec)
70✔
180
   nodetypes.box._init(self, spec)
5,643✔
181
end
182

183
function nodetypes.hbox:__tostring ()
70✔
NEW
184
   return "H<" .. tostring(self.width) .. ">^" .. tostring(self.height) .. "-" .. tostring(self.depth) .. "v"
×
185
end
186

187
function nodetypes.hbox:scaledWidth (line)
70✔
188
   return SU.rationWidth(self:lineContribution(), self.width, line.ratio)
2,806✔
189
end
190

191
function nodetypes.hbox:outputYourself (typesetter, line)
70✔
192
   local outputWidth = self:scaledWidth(line)
1,392✔
193
   if not self.value.glyphString then
1,392✔
194
      return
479✔
195
   end
196
   if typesetter.frame:writingDirection() == "RTL" then
1,826✔
197
      typesetter.frame:advanceWritingDirection(outputWidth)
60✔
198
   end
199
   SILE.outputter:setCursor(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY)
913✔
200
   SILE.outputter:setFont(self.value.options)
913✔
201
   SILE.outputter:drawHbox(self.value, outputWidth)
913✔
202
   if typesetter.frame:writingDirection() ~= "RTL" then
1,826✔
203
      typesetter.frame:advanceWritingDirection(outputWidth)
853✔
204
   end
205
end
206

207
nodetypes.zerohbox = pl.class(nodetypes.hbox)
70✔
208
nodetypes.zerohbox.type = "zerohbox"
35✔
209
nodetypes.zerohbox.value = { glyph = 0 }
35✔
210

211
nodetypes.nnode = pl.class(nodetypes.hbox)
70✔
212
nodetypes.nnode.type = "nnode"
35✔
213
nodetypes.nnode.language = ""
35✔
214
nodetypes.nnode.pal = nil
35✔
215
nodetypes.nnode.nodes = {}
35✔
216

217
function nodetypes.nnode:_init (spec)
70✔
218
   self:super(spec)
1,824✔
219
   if 0 == self.depth:tonumber() then
3,648✔
220
      self.depth = _maxnode(self.nodes, "depth")
3,648✔
221
   end
222
   if 0 == self.height:tonumber() then
3,648✔
223
      self.height = _maxnode(self.nodes, "height")
3,648✔
224
   end
225
   if 0 == self.width:tonumber() then
3,648✔
226
      self.width = SU.sum(SU.map(function (node)
5,472✔
227
         return node.width
1,670✔
228
      end, self.nodes))
3,648✔
229
   end
230
end
231

232
function nodetypes.nnode:__tostring ()
70✔
NEW
233
   return "N<"
×
NEW
234
      .. tostring(self.width)
×
NEW
235
      .. ">^"
×
NEW
236
      .. tostring(self.height)
×
NEW
237
      .. "-"
×
NEW
238
      .. tostring(self.depth)
×
NEW
239
      .. "v("
×
NEW
240
      .. self:toText()
×
NEW
241
      .. ")"
×
242
end
243

244
function nodetypes.nnode:absolute ()
70✔
NEW
245
   return self
×
246
end
247

248
function nodetypes.nnode:outputYourself (typesetter, line)
70✔
249
   -- See typesetter:computeLineRatio() which implements the currently rather messy
250
   -- and probably slightly dubious 'hyphenated' logic.
251
   -- Example: consider the word "out-put".
252
   -- The node queue therefore contains N(out)D(-)N(put) all pointing to the same
253
   -- parent N(output).
254
   if self.parent and not self.parent.hyphenated then
1,269✔
255
      -- When we hit N(out) and are not hyphenated, we output N(output) directly
256
      -- and mark it as used, so as to skip D(-)N(put) afterwards.
257
      -- I guess this was done to ensure proper kerning (vs. outputting each of
258
      -- the nodes separately).
259
      if not self.parent.used then
356✔
260
         self.parent:outputYourself(typesetter, line)
149✔
261
      end
262
      self.parent.used = true
356✔
263
   else
264
      -- It's possible not to have a parent, e.g. N(word) without hyphenation points.
265
      -- Either that, or we have a hyphenated parent but are in the case we are
266
      -- outputting one of the elements e.g. N(out)D(-) [line break] N(put).
267
      -- (ignoring the G(margin) nodes and potentially zerohbox nodes also on either side of the line break)
268
      for _, node in ipairs(self.nodes) do
1,826✔
269
         node:outputYourself(typesetter, line)
913✔
270
      end
271
   end
272
end
273

274
function nodetypes.nnode:toText ()
70✔
275
   return self.text
2✔
276
end
277

278
nodetypes.unshaped = pl.class(nodetypes.nnode)
70✔
279
nodetypes.unshaped.type = "unshaped"
35✔
280

281
function nodetypes.unshaped:_init (spec)
70✔
282
   self:super(spec)
154✔
283
   self.width = nil
154✔
284
end
285

286
function nodetypes.unshaped:__tostring ()
70✔
NEW
287
   return "U(" .. self:toText() .. ")"
×
288
end
289

290
getmetatable(nodetypes.unshaped).__index = function (_, _)
35✔
291
   -- if k == "width" then SU.error("Can't get width of unshaped node", true) end
292
   -- TODO: No idea why porting to proper Penlight classes this ^^^^^^ started
293
   -- killing everything. Perhaps because this function started working and would
294
   -- actually need to return rawget(self, k) or something?
295
end
296

297
function nodetypes.unshaped:shape ()
70✔
298
   local node = SILE.shaper:createNnodes(self.text, self.options)
140✔
299
   for i = 1, #node do
1,665✔
300
      node[i].parent = self.parent
3,050✔
301
   end
302
   return node
140✔
303
end
304

305
function nodetypes.unshaped.outputYourself (_)
70✔
NEW
306
   SU.error("An unshaped node made it to output", true)
×
307
end
308

309
nodetypes.discretionary = pl.class(nodetypes.hbox)
70✔
310

311
nodetypes.discretionary.type = "discretionary"
35✔
312
nodetypes.discretionary.prebreak = {}
35✔
313
nodetypes.discretionary.postbreak = {}
35✔
314
nodetypes.discretionary.replacement = {}
35✔
315
nodetypes.discretionary.used = false
35✔
316

317
function nodetypes.discretionary:__tostring ()
70✔
NEW
318
   return "D("
×
NEW
319
      .. SU.concat(self.prebreak, "")
×
NEW
320
      .. "|"
×
NEW
321
      .. SU.concat(self.postbreak, "")
×
NEW
322
      .. "|"
×
NEW
323
      .. SU.concat(self.replacement, "")
×
NEW
324
      .. ")"
×
325
end
326

327
function nodetypes.discretionary:toText ()
70✔
NEW
328
   return self.used and "-" or "_"
×
329
end
330

331
function nodetypes.discretionary:markAsPrebreak ()
70✔
332
   self.used = true
11✔
333
   if self.parent then
11✔
334
      self.parent.hyphenated = true
11✔
335
   end
336
   self.is_prebreak = true
11✔
337
end
338

339
function nodetypes.discretionary:cloneAsPostbreak ()
70✔
340
   if not self.used then
11✔
NEW
341
      SU.error("Cannot clone a non-used discretionary (previously marked as prebreak)")
×
342
   end
343
   return SILE.types.node.discretionary({
11✔
344
      prebreak = self.prebreak,
11✔
345
      postbreak = self.postbreak,
11✔
346
      replacement = self.replacement,
11✔
347
      parent = self.parent,
11✔
348
      used = true,
349
      is_prebreak = false,
350
   })
11✔
351
end
352

353
function nodetypes.discretionary:outputYourself (typesetter, line)
70✔
354
   -- See typesetter:computeLineRatio() which implements the currently rather
355
   -- messy hyphenated checks.
356
   -- Example: consider the word "out-put-ter".
357
   -- The node queue contains N(out)D(-)N(put)D(-)N(ter) all pointing to the same
358
   -- parent N(output), and here we hit D(-)
359

360
   -- Non-hyphenated parent: when N(out) was hit, we went for outputting
361
   -- the whole parent, so all other elements must now be skipped.
362
   if self.parent and not self.parent.hyphenated then
250✔
363
      return
207✔
364
   end
365

366
   -- It's possible not to have a parent (e.g. on a discretionary directly
367
   -- added in the queue and not coming from the hyphenator logic).
368
   -- Eiher that, or we have a hyphenated parent.
369
   if self.used then
43✔
370
      -- This is the actual hyphenation point.
371
      if self.is_prebreak then
22✔
372
         for _, node in ipairs(self.prebreak) do
22✔
373
            node:outputYourself(typesetter, line)
11✔
374
         end
375
      else
376
         for _, node in ipairs(self.postbreak) do
11✔
NEW
377
            node:outputYourself(typesetter, line)
×
378
         end
379
      end
380
   else
381
      -- This is not the hyphenation point (but another discretionary in the queue)
382
      -- E.g. we were in the case where we have N(out)D(-) [line break] N(out)D(-)N(ter)
383
      -- and now hit the second D(-).
384
      -- Unused discretionaries are obviously replaced.
385
      for _, node in ipairs(self.replacement) do
22✔
386
         node:outputYourself(typesetter, line)
1✔
387
      end
388
   end
389
end
390

391
function nodetypes.discretionary:prebreakWidth ()
70✔
392
   if self.prebw then
574✔
393
      return self.prebw
335✔
394
   end
395
   self.prebw = SILE.types.length()
478✔
396
   for _, node in ipairs(self.prebreak) do
478✔
397
      self.prebw:___add(node.width)
239✔
398
   end
399
   return self.prebw
239✔
400
end
401

402
function nodetypes.discretionary:postbreakWidth ()
70✔
403
   if self.postbw then
68✔
NEW
404
      return self.postbw
×
405
   end
406
   self.postbw = SILE.types.length()
136✔
407
   for _, node in ipairs(self.postbreak) do
68✔
NEW
408
      self.postbw:___add(node.width)
×
409
   end
410
   return self.postbw
68✔
411
end
412

413
function nodetypes.discretionary:replacementWidth ()
70✔
414
   if self.replacew then
331✔
415
      return self.replacew
92✔
416
   end
417
   self.replacew = SILE.types.length()
478✔
418
   for _, node in ipairs(self.replacement) do
242✔
419
      self.replacew:___add(node.width)
3✔
420
   end
421
   return self.replacew
239✔
422
end
423

424
function nodetypes.discretionary:prebreakHeight ()
70✔
425
   if self.prebh then
11✔
NEW
426
      return self.prebh
×
427
   end
428
   self.prebh = _maxnode(self.prebreak, "height")
22✔
429
   return self.prebh
11✔
430
end
431

432
function nodetypes.discretionary:postbreakHeight ()
70✔
433
   if self.postbh then
11✔
NEW
434
      return self.postbh
×
435
   end
436
   self.postbh = _maxnode(self.postbreak, "height")
22✔
437
   return self.postbh
11✔
438
end
439

440
function nodetypes.discretionary:replacementHeight ()
70✔
441
   if self.replaceh then
21✔
NEW
442
      return self.replaceh
×
443
   end
444
   self.replaceh = _maxnode(self.replacement, "height")
42✔
445
   return self.replaceh
21✔
446
end
447

448
function nodetypes.discretionary:replacementDepth ()
70✔
NEW
449
   if self.replaced then
×
NEW
450
      return self.replaced
×
451
   end
NEW
452
   self.replaced = _maxnode(self.replacement, "depth")
×
NEW
453
   return self.replaced
×
454
end
455

456
nodetypes.alternative = pl.class(nodetypes.hbox)
70✔
457

458
nodetypes.alternative.type = "alternative"
35✔
459
nodetypes.alternative.options = {}
35✔
460
nodetypes.alternative.selected = nil
35✔
461

462
function nodetypes.alternative:__tostring ()
70✔
NEW
463
   return "A(" .. SU.concat(self.options, " / ") .. ")"
×
464
end
465

466
function nodetypes.alternative:minWidth ()
70✔
467
   local minW = function (a, b)
NEW
468
      return SU.min(a.width, b.width)
×
469
   end
NEW
470
   return pl.tablex.reduce(minW, self.options)
×
471
end
472

473
function nodetypes.alternative:deltas ()
70✔
NEW
474
   local minWidth = self:minWidth()
×
NEW
475
   local rv = {}
×
NEW
476
   for i = 1, #self.options do
×
NEW
477
      rv[#rv + 1] = self.options[i].width - minWidth
×
478
   end
NEW
479
   return rv
×
480
end
481

482
function nodetypes.alternative:outputYourself (typesetter, line)
70✔
NEW
483
   if self.selected then
×
NEW
484
      self.options[self.selected]:outputYourself(typesetter, line)
×
485
   end
486
end
487

488
nodetypes.glue = pl.class(nodetypes.box)
70✔
489
nodetypes.glue.type = "glue"
35✔
490
nodetypes.glue.discardable = true
35✔
491

492
function nodetypes.glue:__tostring ()
70✔
NEW
493
   return (self.explicit and "E:" or "") .. "G<" .. tostring(self.width) .. ">"
×
494
end
495

496
function nodetypes.glue.toText (_)
70✔
NEW
497
   return " "
×
498
end
499

500
function nodetypes.glue:outputYourself (typesetter, line)
70✔
501
   local outputWidth = SU.rationWidth(self.width:absolute(), self.width:absolute(), line.ratio)
3,711✔
502
   typesetter.frame:advanceWritingDirection(outputWidth)
1,237✔
503
end
504

505
-- A hfillglue is just a glue with infinite stretch.
506
-- (Convenience so callers do not have to know what infinity is.)
507
nodetypes.hfillglue = pl.class(nodetypes.glue)
70✔
508
function nodetypes.hfillglue:_init (spec)
70✔
509
   self:super(spec)
83✔
510
   self.width = SILE.types.length(self.width.length, infinity, self.width.shrink)
166✔
511
end
512

513
-- A hssglue is just a glue with infinite stretch and shrink.
514
-- (Convenience so callers do not have to know what infinity is.)
515
nodetypes.hssglue = pl.class(nodetypes.glue)
70✔
516
function nodetypes.hssglue:_init (spec)
70✔
NEW
517
   self:super(spec)
×
NEW
518
   self.width = SILE.types.length(self.width.length, infinity, infinity)
×
519
end
520

521
nodetypes.kern = pl.class(nodetypes.glue)
70✔
522
nodetypes.kern.type = "kern" -- Perhaps some smell here, see comment on vkern
35✔
523
nodetypes.kern.discardable = false
35✔
524

525
function nodetypes.kern:__tostring ()
70✔
NEW
526
   return "K<" .. tostring(self.width) .. ">"
×
527
end
528

529
nodetypes.vglue = pl.class(nodetypes.box)
70✔
530
nodetypes.vglue.type = "vglue"
35✔
531
nodetypes.vglue.discardable = true
35✔
532
nodetypes.vglue._default_length = "height"
35✔
533
nodetypes.vglue.adjustment = nil
35✔
534

535
function nodetypes.vglue:_init (spec)
70✔
536
   self.adjustment = SILE.measurement()
816✔
537
   self:super(spec)
408✔
538
end
539

540
function nodetypes.vglue:__tostring ()
70✔
NEW
541
   return (self.explicit and "E:" or "") .. "VG<" .. tostring(self.height) .. ">"
×
542
end
543

544
function nodetypes.vglue:adjustGlue (adjustment)
70✔
545
   self.adjustment = adjustment
249✔
546
end
547

548
function nodetypes.vglue:outputYourself (typesetter, line)
70✔
549
   typesetter.frame:advancePageDirection(line.height:absolute() + line.depth:absolute() + self.adjustment)
1,515✔
550
end
551

552
function nodetypes.vglue:unbox ()
70✔
553
   return { self }
2✔
554
end
555

556
-- A vfillglue is just a vglue with infinite stretch.
557
-- (Convenience so callers do not have to know what infinity is.)
558
nodetypes.vfillglue = pl.class(nodetypes.vglue)
70✔
559
function nodetypes.vfillglue:_init (spec)
70✔
560
   self:super(spec)
50✔
561
   self.height = SILE.types.length(self.width.length, infinity, self.width.shrink)
100✔
562
end
563

564
-- A vssglue is just a vglue with infinite stretch and shrink.
565
-- (Convenience so callers do not have to know what infinity is.)
566
nodetypes.vssglue = pl.class(nodetypes.vglue)
70✔
567
function nodetypes.vssglue:_init (spec)
70✔
NEW
568
   self:super(spec)
×
NEW
569
   self.height = SILE.types.length(self.width.length, infinity, infinity)
×
570
end
571

572
nodetypes.zerovglue = pl.class(nodetypes.vglue)
70✔
573

574
nodetypes.vkern = pl.class(nodetypes.vglue)
70✔
575
-- FIXME TODO
576
-- Here we cannot do:
577
--   nodetypes.vkern.type = "vkern"
578
-- It cannot be typed as "vkern" as the pagebuilder doesn't check is_vkern.
579
-- So it's just a vglue currrenty, marked as not discardable...
580
-- But on the other hand, nodetypes.kern is typed "kern" and is not a glue...
581
-- Frankly, the discardable/explicit flags and the types are too
582
-- entangled and point towards a more general design issue.
583
-- N.B. this vkern node is only used in the linespacing package so far.
584
nodetypes.vkern.discardable = false
35✔
585

586
function nodetypes.vkern:__tostring ()
70✔
NEW
587
   return "VK<" .. tostring(self.height) .. ">"
×
588
end
589

590
nodetypes.penalty = pl.class(nodetypes.box)
70✔
591
nodetypes.penalty.type = "penalty"
35✔
592
nodetypes.penalty.discardable = true
35✔
593
nodetypes.penalty.penalty = 0
35✔
594

595
function nodetypes.penalty:_init (spec)
70✔
596
   self:super(spec)
333✔
597
   if type(spec) ~= "table" then
333✔
598
      self.penalty = SU.cast("number", spec)
564✔
599
   end
600
end
601

602
function nodetypes.penalty:__tostring ()
70✔
NEW
603
   return "P(" .. tostring(self.penalty) .. ")"
×
604
end
605

606
function nodetypes.penalty.outputYourself (_) end
200✔
607

608
function nodetypes.penalty.toText (_)
70✔
NEW
609
   return "(!)"
×
610
end
611

612
function nodetypes.penalty:unbox ()
70✔
NEW
613
   return { self }
×
614
end
615

616
nodetypes.vbox = pl.class(nodetypes.box)
70✔
617
nodetypes.vbox.type = "vbox"
35✔
618
nodetypes.vbox.nodes = {}
35✔
619
nodetypes.vbox._default_length = "height"
35✔
620

621
function nodetypes.vbox:_init (spec)
70✔
622
   self.nodes = {}
182✔
623
   self:super(spec)
182✔
624
   self.depth = _maxnode(self.nodes, "depth")
364✔
625
   self.height = _maxnode(self.nodes, "height")
364✔
626
end
627

628
function nodetypes.vbox:__tostring ()
70✔
NEW
629
   return "VB<" .. tostring(self.height) .. "|" .. self:toText() .. "v" .. tostring(self.depth) .. ")"
×
630
end
631

632
function nodetypes.vbox:toText ()
70✔
NEW
633
   return "VB["
×
NEW
634
      .. SU.concat(
×
NEW
635
         SU.map(function (node)
×
NEW
636
            return node:toText()
×
NEW
637
         end, self.nodes),
×
638
         ""
639
      )
NEW
640
      .. "]"
×
641
end
642

643
function nodetypes.vbox:outputYourself (typesetter, line)
70✔
644
   typesetter.frame:advancePageDirection(self.height)
174✔
645
   local initial = true
174✔
646
   for _, node in ipairs(self.nodes) do
3,453✔
647
      if not (initial and (node.is_glue or node.is_penalty)) then
3,279✔
648
         initial = false
3,279✔
649
         node:outputYourself(typesetter, line)
3,279✔
650
      end
651
   end
652
   typesetter.frame:advancePageDirection(self.depth)
174✔
653
   typesetter.frame:newLine()
174✔
654
end
655

656
function nodetypes.vbox:unbox ()
70✔
657
   for i = 1, #self.nodes do
3✔
658
      if self.nodes[i].is_vbox or self.nodes[i].is_vglue then
3✔
659
         return self.nodes
3✔
660
      end
661
   end
NEW
662
   return { self }
×
663
end
664

665
function nodetypes.vbox:append (box)
70✔
666
   local nodes = box
7✔
667
   if not box then
7✔
NEW
668
      SU.error("nil box given", true)
×
669
   end
670
   if nodes.type then
7✔
671
      nodes = box:unbox()
8✔
672
   end
673
   self.height = self.height:absolute()
14✔
674
   self.height:___add(self.depth)
7✔
675
   local lastdepth = SILE.types.length()
7✔
676
   for i = 1, #nodes do
187✔
677
      table.insert(self.nodes, nodes[i])
180✔
678
      self.height:___add(nodes[i].height)
180✔
679
      self.height:___add(nodes[i].depth:absolute())
360✔
680
      if nodes[i].is_vbox then
180✔
681
         lastdepth = nodes[i].depth
78✔
682
      end
683
   end
684
   self.height:___sub(lastdepth)
7✔
685
   self.ratio = 1
7✔
686
   self.depth = lastdepth
7✔
687
end
688

689
nodetypes.migrating = pl.class(nodetypes.hbox)
70✔
690
nodetypes.migrating.type = "migrating"
35✔
691
nodetypes.migrating.material = {}
35✔
692
nodetypes.migrating.value = {}
35✔
693
nodetypes.migrating.nodes = {}
35✔
694

695
function nodetypes.migrating:__tostring ()
70✔
NEW
696
   return "<M: " .. tostring(self.material) .. ">"
×
697
end
698

699
local _deprecated_nodefactory = {
35✔
700
   newHbox = true,
701
   newNnode = true,
702
   newUnshaped = true,
703
   newDisc = true,
704
   disc = true,
705
   newAlternative = true,
706
   newGlue = true,
707
   newKern = true,
708
   newVglue = true,
709
   newVKern = true,
710
   newPenalty = true,
711
   newDiscretionary = true,
712
   newVbox = true,
713
   newMigrating = true,
714
   zeroGlue = true,
715
   hfillGlue = true,
716
   vfillGlue = true,
717
   hssGlue = true,
718
   vssGlue = true,
719
   zeroHbox = true,
720
   zeroVglue = true,
721
}
722

723
setmetatable(nodetypes, {
70✔
724
   __index = function (_, prop)
NEW
725
      if _deprecated_nodefactory[prop] then
×
NEW
726
         SU.deprecated(
×
NEW
727
            "SILE.types.node." .. prop,
×
NEW
728
            "SILE.types.node." .. prop:match("n?e?w?(.*)"):lower(),
×
729
            "0.10.0",
730
            "0.14.0"
731
         )
NEW
732
      elseif type(prop) == "number" then -- luacheck: ignore 542
×
733
      -- Likely an attempt to iterate, inspect, or dump the table, sort of safe to ignore
734
      else
NEW
735
         SU.error("Attempt to access non-existent SILE.types.node." .. prop)
×
736
      end
737
   end,
738
})
739

740
return nodetypes
35✔
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