sile 0.15.11

Simon’s Improved Layout Engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
SILE.settings:declare({
   parameter = "linebreak.parShape",
   type = "boolean",
   default = false,
   help = "If set to true, the paragraph shaping method is activated.",
})
SILE.settings:declare({ parameter = "linebreak.tolerance", type = "integer or nil", default = 500 })
SILE.settings:declare({ parameter = "linebreak.pretolerance", type = "integer or nil", default = 100 })
SILE.settings:declare({ parameter = "linebreak.hangIndent", type = "measurement", default = 0 })
SILE.settings:declare({ parameter = "linebreak.hangAfter", type = "integer or nil", default = nil })
SILE.settings:declare({
   parameter = "linebreak.adjdemerits",
   type = "integer",
   default = 10000,
   help = "Additional demerits which are accumulated in the course of paragraph building when two consecutive lines are visually incompatible. In these cases, one line is built with much space for justification, and the other one with little space.",
})
SILE.settings:declare({ parameter = "linebreak.looseness", type = "integer", default = 0 })
SILE.settings:declare({ parameter = "linebreak.prevGraf", type = "integer", default = 0 })
SILE.settings:declare({ parameter = "linebreak.emergencyStretch", type = "measurement", default = 0 })
SILE.settings:declare({ parameter = "linebreak.doLastLineFit", type = "boolean", default = false }) -- unimplemented
SILE.settings:declare({ parameter = "linebreak.linePenalty", type = "integer", default = 10 })
SILE.settings:declare({ parameter = "linebreak.hyphenPenalty", type = "integer", default = 50 })
SILE.settings:declare({ parameter = "linebreak.doubleHyphenDemerits", type = "integer", default = 10000 })
SILE.settings:declare({ parameter = "linebreak.finalHyphenDemerits", type = "integer", default = 5000 })

-- doubleHyphenDemerits
-- hyphenPenalty

local classes = { "tight", "decent", "loose", "veryLoose" }
local passSerial = 0
local awful_bad = 1073741823
local inf_bad = 10000
local ejectPenalty = -inf_bad
local lineBreak = {}

--[[
  Basic control flow:
  doBreak:
    init
    for each node:
      checkForLegalBreak
        tryBreak
          createNewActiveNodes
          considerDemerits
            deactivateR (or) recordFeasible
    tryFinalBreak
    postLineBreak
]]

local param = function (key)
   local value = SILE.settings:get("linebreak." .. key)
   return type(value) == "table" and value:absolute() or value
end

-- Routines here will be called thousands of times; we micro-optimize
-- to avoid debugging and concat calls.
local debugging = false

function lineBreak:init ()
   self:trimGlue() -- 842
   -- 849
   self.activeWidth = SILE.types.length()
   self.curActiveWidth = SILE.types.length()
   self.breakWidth = SILE.types.length()
   -- 853
   local rskip = (SILE.settings:get("document.rskip") or SILE.types.node.glue()).width:absolute()
   local lskip = (SILE.settings:get("document.lskip") or SILE.types.node.glue()).width:absolute()
   self.background = rskip + lskip
   -- 860
   self.bestInClass = {}
   for i = 1, #classes do
      self.bestInClass[classes[i]] = {
         minimalDemerits = awful_bad,
      }
   end
   self.minimumDemerits = awful_bad
   self:setupLineLengths()
end

function lineBreak:trimGlue () -- 842
   local nodes = self.nodes
   if nodes[#nodes].is_glue then
      nodes[#nodes] = nil
   end
   nodes[#nodes + 1] = SILE.types.node.penalty(inf_bad)
end

-- NOTE FOR DEVELOPERS: this method is called when the linebreak.parShape
-- setting is true. The arguments passed are self (the linebreaker instance)
-- and a counter representing the current line number.
--
-- The default implementation does nothing but waste a function call, resulting
-- in normal paragraph shapes. Extended paragraph shapes are intended to be
-- provided by overriding this method.
--
-- The expected return is three values, any of which may be nil to use default
-- values or a measurement to override the defaults. The values are considered
-- as left, width, and right respectively.
--
-- Since self.hsize holds the current line width, these three values should add
-- up to the that total. Returning values that don't add up may produce
-- unexpected results.
--
-- TeX wizards shall also note that this is slightly different from
-- Knuth's definition "nline l1 i1 l2 i2 ... lN iN".
function lineBreak:parShape (_)
   return 0, self.hsize, 0
end

local parShapeCache = {}

local grantLeftoverWidth = function (hsize, l, w, r)
   local width = SILE.types.measurement(w or hsize)
   if not w and l then
      width = width - SILE.types.measurement(l)
   end
   if not w and r then
      width = width - SILE.types.measurement(r)
   end
   local remaining = hsize:tonumber() - width:tonumber()
   local left = SU.cast("number", l or (r and (remaining - SU.cast("number", r))) or 0)
   local right = SU.cast("number", r or (l and (remaining - SU.cast("number", l))) or remaining)
   return left, width, right
end

-- Wrap linebreak:parShape in a memoized table for fast access
function lineBreak:parShapeCache (n)
   local cache = parShapeCache[n]
   if not cache then
      local l, w, r = self:parShape(n)
      local left, width, right = grantLeftoverWidth(self.hsize, l, w, r)
      cache = { left, width, right }
   end
   return cache[1], cache[2], cache[3]
end

function lineBreak:parShapeCacheClear ()
   pl.tablex.clear(parShapeCache)
end

function lineBreak:setupLineLengths () -- 874
   self.parShaping = param("parShape") or false
   if self.parShaping then
      self.lastSpecialLine = nil
      self.easy_line = nil
   else
      self.hangAfter = param("hangAfter") or 0
      self.hangIndent = param("hangIndent"):tonumber()
      if self.hangIndent == 0 then
         self.lastSpecialLine = 0
         self.secondWidth = self.hsize or SU.error("No hsize")
      else -- 875
         self.lastSpecialLine = math.abs(self.hangAfter)
         if self.hangAfter < 0 then
            self.secondWidth = self.hsize or SU.error("No hsize")
            self.firstWidth = self.hsize - math.abs(self.hangIndent)
         else
            self.firstWidth = self.hsize or SU.error("No hsize")
            self.secondWidth = self.hsize - math.abs(self.hangIndent)
         end
      end
      if param("looseness") == 0 then
         self.easy_line = self.lastSpecialLine
      else
         self.easy_line = awful_bad
      end
      -- self.easy_line = awful_bad
   end
end

function lineBreak:tryBreak () -- 855
   local pi, breakType
   local node = self.nodes[self.place]
   if not node then
      pi = ejectPenalty
      breakType = "hyphenated"
   elseif node.is_discretionary then
      breakType = "hyphenated"
      pi = param("hyphenPenalty")
   else
      breakType = "unhyphenated"
      pi = node.penalty or 0
   end
   if debugging then
      SU.debug("break", "Trying a", breakType, "break p =", pi)
   end
   self.no_break_yet = true -- We have to store all this state crap in the object, or it's global variables all the way
   self.prev_prev_r = nil
   self.prev_r = self.activeListHead
   self.old_l = 0
   self.r = nil
   self.curActiveWidth = SILE.types.length(self.activeWidth)
   while true do
      while true do -- allows "break" to function as "continue"
         self.r = self.prev_r.next
         if debugging then
            SU.debug(
               "break",
               "We have moved the link  forward, ln is now",
               self.r.type == "delta" and "XX" or self.r.lineNumber
            )
         end
         if self.r.type == "delta" then -- 858
            if debugging then
               SU.debug("break", " Adding delta node width of", self.r.width)
            end
            self.curActiveWidth:___add(self.r.width)
            self.prev_prev_r = self.prev_r
            self.prev_r = self.r
            break
         end
         -- 861
         if self.r.lineNumber > self.old_l then
            if debugging then
               SU.debug("break", "Minimum demerits =", self.minimumDemerits)
            end
            if self.minimumDemerits < awful_bad and (self.old_l ~= self.easy_line or self.r == self.activeListHead) then
               self:createNewActiveNodes(breakType)
            end
            if self.r == self.activeListHead then
               if debugging then
                  SU.debug("break", "<- tryBreak")
               end
               return
            end
            -- 876
            if self.easy_line and self.r.lineNumber > self.easy_line then
               self.lineWidth = self.secondWidth
               self.old_l = awful_bad - 1
            else
               self.old_l = self.r.lineNumber
               if self.lastSpecialLine and self.r.lineNumber > self.lastSpecialLine then
                  self.lineWidth = self.secondWidth
               elseif self.parShaping then
                  local _
                  _, self.lineWidth, _ = self:parShapeCache(self.r.lineNumber)
               else
                  self.lineWidth = self.firstWidth
               end
            end
            if debugging then
               SU.debug("break", "line width =", self.lineWidth)
            end
         end
         if debugging then
            SU.debug("break", " ---> (2) cuaw is", self.curActiveWidth)
            SU.debug("break", " ---> aw is", self.activeWidth)
         end
         self:considerDemerits(pi, breakType)
         if debugging then
            SU.debug("break", " <--- cuaw is", self.curActiveWidth)
            SU.debug("break", " <--- aw is ", self.activeWidth)
         end
      end
   end
end

-- Note: This function gets called a lot and to optimize it we're assuming that
-- the lengths being passed are already absolutized. This is not a safe
-- assumption to make universally.
local function fitclass (self, shortfall)
   shortfall = shortfall.amount
   local badness, class
   local stretch = self.curActiveWidth.stretch.amount
   local shrink = self.curActiveWidth.shrink.amount
   if shortfall > 0 then
      if shortfall > 110 and stretch < 25 then
         badness = inf_bad
      else
         badness = SU.rateBadness(inf_bad, shortfall, stretch)
      end
      if badness > 99 then
         class = "veryLoose"
      elseif badness > 12 then
         class = "loose"
      else
         class = "decent"
      end
   else
      shortfall = -shortfall
      if shortfall > shrink then
         badness = inf_bad + 1
      else
         badness = SU.rateBadness(inf_bad, shortfall, shrink)
      end
      if badness > 12 then
         class = "tight"
      else
         class = "decent"
      end
   end
   return badness, class
end

function lineBreak:tryAlternatives (from, to)
   local altSizes = {}
   local alternates = {}
   for i = from, to do
      if self.nodes[i] and self.nodes[i].is_alternative then
         alternates[#alternates + 1] = self.nodes[i]
         altSizes[#altSizes + 1] = #self.nodes[i].options
      end
   end
   if #alternates == 0 then
      return
   end
   local localMinimum = awful_bad
   -- local selectedShortfall
   local shortfall = self.lineWidth - self.curActiveWidth
   if debugging then
      SU.debug("break", "Shortfall was ", shortfall)
   end
   for combination in SU.allCombinations(altSizes) do
      local addWidth = 0
      for i = 1, #alternates do
         local alternative = alternates[i]
         addWidth = (addWidth + alternative.options[combination[i]].width - alternative:minWidth())
         if debugging then
            SU.debug("break", alternative.options[combination[i]], " width", addWidth)
         end
      end
      local ss = shortfall - addWidth
      -- Warning, assumes abosolute
      local badness =
         SU.rateBadness(inf_bad, ss.length.amount, self.curActiveWidth[ss > 0 and "stretch" or "shrink"].length.amount)
      if debugging then
         SU.debug("break", "  badness of", ss, "(", self.curActiveWidth, ") is", badness)
      end
      if badness < localMinimum then
         self.r.alternates = alternates
         self.r.altSelections = combination
         -- selectedShortfall = addWidth
         localMinimum = badness
      end
   end
   if debugging then
      SU.debug("break", "Choosing ", alternates[1].options[self.r.altSelections[1]])
   end
   -- self.curActiveWidth:___add(selectedShortfall)
   shortfall = self.lineWidth - self.curActiveWidth
   if debugging then
      SU.debug("break", "Is now ", shortfall)
   end
end

function lineBreak:considerDemerits (pi, breakType) -- 877
   self.artificialDemerits = false
   local nodeStaysActive = false
   -- self:dumpActiveRing()
   if self.seenAlternatives then
      self:tryAlternatives(
         self.r.prevBreak and self.r.prevBreak.curBreak or 1,
         self.r.curBreak and self.r.curBreak or 1
      )
   end
   local shortfall = self.lineWidth - self.curActiveWidth
   self.badness, self.fitClass = fitclass(self, shortfall)
   if debugging then
      SU.debug("break", self.badness, self.fitClass)
   end
   if self.badness > inf_bad or pi == ejectPenalty then
      if
         self.finalpass
         and self.minimumDemerits == awful_bad
         and self.r.next == self.activeListHead
         and self.prev_r == self.activeListHead
      then
         self.artificialDemerits = true
      else
         if self.badness > self.threshold then
            self:deactivateR()
            return
         end
      end
   else
      self.prev_r = self.r
      if self.badness > self.threshold then
         return
      end
      nodeStaysActive = true
   end

   local _shortfall = shortfall:tonumber()
   local function shortfallratio (metric)
      local prop = self.curActiveWidth[metric]:tonumber()
      local factor = prop ~= 0 and prop or awful_bad
      return _shortfall / factor
   end
   self.lastRatio = shortfallratio(_shortfall > 0 and "stretch" or "shrink")
   self:recordFeasible(pi, breakType)
   if not nodeStaysActive then
      self:deactivateR()
   end
end

function lineBreak:deactivateR () -- 886
   if debugging then
      SU.debug("break", " Deactivating r (" .. self.r.type .. ")")
   end
   self.prev_r.next = self.r.next
   if self.prev_r == self.activeListHead then
      -- 887
      self.r = self.activeListHead.next
      if self.r.type == "delta" then
         self.activeWidth:___add(self.r.width)
         self.curActiveWidth = SILE.types.length(self.activeWidth)
         self.activeListHead.next = self.r.next
      end
      if debugging then
         SU.debug("break", "  Deactivate, branch 1")
      end
   else
      if self.prev_r.type == "delta" then
         self.r = self.prev_r.next
         if self.r == self.activeListHead then
            self.curActiveWidth:___sub(self.prev_r.width)
            -- FIXME It was crashing here, so changed from:
            -- self.curActiveWidth:___sub(self.r.width)
            -- But I'm not so sure reading Knuth here...
            self.prev_prev_r.next = self.activeListHead
            self.prev_r = self.prev_prev_r
         elseif self.r.type == "delta" then
            self.curActiveWidth:___add(self.r.width)
            self.prev_r.width:___add(self.r.width)
            self.prev_r.next = self.r.next
         end
      end
      if debugging then
         SU.debug("break", "  Deactivate, branch 2")
      end
   end
end

function lineBreak:computeDemerits (pi, breakType)
   if self.artificialDemerits then
      return 0
   end
   local demerit = param("linePenalty") + self.badness
   if math.abs(demerit) >= 10000 then
      demerit = 100000000
   else
      demerit = demerit * demerit
   end
   if pi > 0 then
      demerit = demerit + pi * pi
   -- elseif pi == 0 then
   --   -- do nothing
   elseif pi > ejectPenalty then
      demerit = demerit - pi * pi
   end
   if breakType == "hyphenated" and self.r.type == "hyphenated" then
      if self.nodes[self.place] then
         demerit = demerit + param("doubleHyphenDemerits")
      else
         demerit = demerit + param("finalHyphenDemerits")
      end
   end
   -- XXX adjDemerits not added here
   return demerit
end

function lineBreak:recordFeasible (pi, breakType) -- 881
   local demerit = lineBreak:computeDemerits(pi, breakType)
   if debugging then
      if self.nodes[self.place] then
         SU.debug(
            "break",
            "@",
            self.nodes[self.place],
            "via @@",
            (self.r.serial or "0"),
            "badness =",
            self.badness,
            "demerit =",
            demerit
         ) -- 882
      else
         SU.debug("break", "@ \\par via @@")
      end
      SU.debug("break", " fit class =", self.fitClass)
   end
   demerit = demerit + self.r.totalDemerits
   if demerit <= self.bestInClass[self.fitClass].minimalDemerits then
      self.bestInClass[self.fitClass] = {
         minimalDemerits = demerit,
         node = self.r.serial and self.r,
         line = self.r.lineNumber,
      }
      -- XXX do last line fit
      if demerit < self.minimumDemerits then
         self.minimumDemerits = demerit
      end
   end
end

function lineBreak:createNewActiveNodes (breakType) -- 862
   if self.no_break_yet then
      -- 863
      self.no_break_yet = false
      self.breakWidth = SILE.types.length(self.background)
      local place = self.place
      local node = self.nodes[place]
      if node and node.is_discretionary then -- 866
         self.breakWidth:___add(node:prebreakWidth())
         self.breakWidth:___add(node:postbreakWidth())
         self.breakWidth:___sub(node:replacementWidth())
      end
      while self.nodes[place] and not self.nodes[place].is_box do
         if self.sideways and self.nodes[place].height then
            self.breakWidth:___sub(self.nodes[place].height)
            self.breakWidth:___sub(self.nodes[place].depth)
         elseif self.nodes[place].width then -- We use the fact that (a) nodes know if they have width and (b) width subtraction is polymorphic
            self.breakWidth:___sub(self.nodes[place]:lineContribution())
         end
         place = place + 1
      end
      if debugging then
         SU.debug("break", "Value of breakWidth =", self.breakWidth)
      end
   end
   -- 869 (Add a new delta node)
   if self.prev_r.type == "delta" then
      self.prev_r.width:___sub(self.curActiveWidth)
      self.prev_r.width:___add(self.breakWidth)
   elseif self.prev_r == self.activeListHead then
      self.activeWidth = SILE.types.length(self.breakWidth)
   else
      local newDelta = { next = self.r, type = "delta", width = self.breakWidth - self.curActiveWidth }
      if debugging then
         SU.debug("break", "Added new delta node =", newDelta.width)
      end
      self.prev_r.next = newDelta
      self.prev_prev_r = self.prev_r
      self.prev_r = newDelta
   end
   if math.abs(self.adjdemerits) >= (awful_bad - self.minimumDemerits) then
      self.minimumDemerits = awful_bad - 1
   else
      self.minimumDemerits = self.minimumDemerits + math.abs(self.adjdemerits)
   end

   for i = 1, #classes do
      local class = classes[i]
      local best = self.bestInClass[class]
      local value = best.minimalDemerits
      if debugging then
         SU.debug("break", "Class is", class, "Best value here is", value)
      end

      if value <= self.minimumDemerits then
         -- 871: this is what creates new active notes
         passSerial = passSerial + 1

         local newActive = {
            type = breakType,
            next = self.r,
            curBreak = self.place,
            prevBreak = best.node,
            serial = passSerial,
            ratio = self.lastRatio,
            lineNumber = best.line + 1,
            fitness = class,
            totalDemerits = value,
         }
         -- DoLastLineFit? 1636 XXX
         self.prev_r.next = newActive
         self.prev_r = newActive
         self:dumpBreakNode(newActive)
      end
      self.bestInClass[class] = { minimalDemerits = awful_bad }
   end

   self.minimumDemerits = awful_bad
   -- 870
   if self.r ~= self.activeListHead then
      local newDelta = { next = self.r, type = "delta", width = self.curActiveWidth - self.breakWidth }
      self.prev_r.next = newDelta
      self.prev_prev_r = self.prev_r
      self.prev_r = newDelta
   end
end

function lineBreak:dumpBreakNode (node)
   if not SU.debugging("break") then
      return
   end
   SU.debug("break", lineBreak:describeBreakNode(node))
end

function lineBreak:describeBreakNode (node)
   --SU.debug("break", "@@", b.serial, ": line", b.lineNumber - 1, ".", b.fitness, b.type, "t=", b.totalDemerits, "-> @@", b.prevBreak and b.prevBreak.serial or "0")
   if node.sentinel then
      return node.sentinel
   end
   if node.type == "delta" then
      return "delta " .. node.width .. "pt"
   end
   local before = self.nodes[node.curBreak - 1]
   local after = self.nodes[node.curBreak + 1]
   local from = node.prevBreak and node.prevBreak.curBreak or 1
   local to = node.curBreak
   return ('b %s-%s "%s | %s" [%s, %s]'):format(
      from,
      to,
      before and before:toText() or "",
      after and after:toText() or "",
      node.totalDemerits,
      node.fitness
   )
end

-- NOTE: this function is called many thousands of times even in single
-- page documents. Speed is more important than pretty code here.
function lineBreak:checkForLegalBreak (node) -- 892
   if debugging then
      SU.debug("break", "considering node " .. node)
   end
   local previous = self.nodes[self.place - 1]
   if node.is_alternative then
      self.seenAlternatives = true
   end
   if self.sideways and node.is_box then
      self.activeWidth:___add(node.height)
      self.activeWidth:___add(node.depth)
   elseif self.sideways and node.is_vglue then
      if previous and previous.is_box then
         self:tryBreak()
      end
      self.activeWidth:___add(node.height)
      self.activeWidth:___add(node.depth)
   elseif node.is_alternative then
      self.activeWidth:___add(node:minWidth())
   elseif node.is_box then
      self.activeWidth:___add(node:lineContribution())
   elseif node.is_glue then
      -- 894 (We removed the auto_breaking parameter)
      if previous and previous.is_box then
         self:tryBreak()
      end
      self.activeWidth:___add(node.width)
   elseif node.is_kern then
      self.activeWidth:___add(node.width)
   elseif node.is_discretionary then -- 895
      self.activeWidth:___add(node:prebreakWidth())
      self:tryBreak()
      self.activeWidth:___sub(node:prebreakWidth())
      self.activeWidth:___add(node:replacementWidth())
   elseif node.is_penalty then
      self:tryBreak()
   end
end

function lineBreak:tryFinalBreak () -- 899
   -- XXX TeX has self:tryBreak() here. But this doesn't seem to work
   -- for us. If we call tryBreak(), we end up demoting all break points
   -- to veryLoose (possibly because the active width gets reset - why?).
   -- This means we end up doing unnecessary passes.
   -- However, there doesn't seem to be any downside to not calling it
   -- (how scary is that?) so I have removed it for now. With this
   -- "fix", we only perform hyphenation and emergency passes when necessary
   -- instead of every single time. If things go strange with the break
   -- algorithm in the future, this should be the first place to look!
   -- self:tryBreak()
   if self.activeListHead.next == self.activeListHead then
      return
   end
   self.r = self.activeListHead.next
   local fewestDemerits = awful_bad
   repeat
      if self.r.type ~= "delta" and self.r.totalDemerits < fewestDemerits then
         fewestDemerits = self.r.totalDemerits
         self.bestBet = self.r
      end
      self.r = self.r.next
   until self.r == self.activeListHead
   if param("looseness") == 0 then
      return true
   end
   -- XXX node 901 not implemented
   if self.actualLooseness == param("looseness") or self.finalpass then
      return true
   end
end

function lineBreak:doBreak (nodes, hsize, sideways)
   passSerial = 1
   debugging = SILE.debugFlags["break"]
   self.seenAlternatives = false
   self.nodes = nodes
   self.hsize = hsize
   self.sideways = sideways
   self:init()
   self.adjdemerits = param("adjdemerits")
   self.threshold = param("pretolerance")
   if self.threshold >= 0 then
      self.pass = "first"
      self.finalpass = false
   else
      self.threshold = param("tolerance")
      self.pass = "second"
      self.finalpass = param("emergencyStretch") <= 0
   end
   -- 889
   while 1 do
      if debugging then
         SU.debug("break", "@", self.pass, "pass")
      end
      if self.threshold > inf_bad then
         self.threshold = inf_bad
      end
      if self.pass == "second" then
         self.nodes = SILE.hyphenate(self.nodes)
         SILE.typesetter.state.nodes = self.nodes -- Horrible breaking of separation of concerns here. :-(
      end
      -- 890
      self.activeListHead = {
         sentinel = "START",
         type = "hyphenated",
         lineNumber = awful_bad,
         subtype = 0,
      } -- 846
      self.activeListHead.next = {
         sentinel = "END",
         type = "unhyphenated",
         fitness = "decent",
         next = self.activeListHead,
         lineNumber = param("prevGraf") + 1,
         totalDemerits = 0,
      }

      -- Not doing 1630
      self.activeWidth = SILE.types.length(self.background)

      self.place = 1
      while self.nodes[self.place] and self.activeListHead.next ~= self.activeListHead do
         self:checkForLegalBreak(self.nodes[self.place])
         self.place = self.place + 1
      end
      if self.place > #self.nodes then
         if self:tryFinalBreak() then
            break
         end
      end
      -- (Not doing 891)
      if self.pass ~= "second" then
         self.pass = "second"
         self.threshold = param("tolerance")
      else
         self.pass = "emergency"
         self.background.stretch:___add(param("emergencyStretch"))
         self.finalpass = true
      end
   end
   -- Not doing 1638
   return self:postLineBreak()
end

function lineBreak:postLineBreak () -- 903
   local p = self.bestBet
   local breaks = {}
   local line = 1

   local nbLines = 0
   local p2 = p
   repeat
      nbLines = nbLines + 1
      p2 = p2.prevBreak
   until not p2

   repeat
      local left, _, right
      -- SILE handles the actual line width differently than TeX,
      -- so below always return a width of self.hsize. Would they
      -- be needed at some point, the exact width are commented out
      -- below.
      if self.parShaping then
         left, _, right = self:parShapeCache(nbLines + 1 - line)
      else
         if self.hangAfter == 0 then
            -- width = self.hsize
            left = 0
            right = 0
         else
            local indent
            if self.hangAfter > 0 then
               -- width = line > nbLines - self.hangAfter and self.firstWidth or self.secondWidth
               indent = line > nbLines - self.hangAfter and 0 or self.hangIndent
            else
               -- width = line > nbLines + self.hangAfter and self.firstWidth or self.secondWidth
               indent = line > nbLines + self.hangAfter and self.hangIndent or 0
            end
            if indent > 0 then
               left = indent
               right = 0
            else
               left = 0
               right = -indent
            end
         end
      end

      table.insert(breaks, 1, {
         position = p.curBreak,
         width = self.hsize,
         left = left,
         right = right,
      })
      if p.alternates then
         for i = 1, #p.alternates do
            p.alternates[i].selected = p.altSelections[i]
            p.alternates[i].width = p.alternates[i].options[p.altSelections[i]].width
         end
      end
      p = p.prevBreak
      line = line + 1
   until not p
   self:parShapeCacheClear()
   return breaks
end

function lineBreak:dumpActiveRing ()
   local p = self.activeListHead
   if not SILE.quiet then
      io.stderr:write("\n")
   end
   repeat
      if not SILE.quiet then
         if p == self.r then
            io.stderr:write("-> ")
         else
            io.stderr:write("   ")
         end
      end
      SU.debug("break", lineBreak:describeBreakNode(p))
      p = p.next
   until p == self.activeListHead
end

return lineBreak