sile 0.14.8

Simon’s Improved Layout Engine
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
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.length()
  self.curActiveWidth = SILE.length()
  self.breakWidth = SILE.length()
  -- 853
  local rskip = (SILE.settings:get("document.rskip") or SILE.nodefactory.glue()).width:absolute()
  local lskip = (SILE.settings:get("document.lskip") or SILE.nodefactory.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.nodefactory.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 slighty 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.measurement(w or hsize)
  if not w and l then width = width - SILE.measurement(l) end
  if not w and r then width = width - SILE.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.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", "Mimimum 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 = " .. tostring(self.lineWidth)) end
      end
      if debugging then
        SU.debug("break", " ---> (2) cuaw is " .. tostring(self.curActiveWidth))
        SU.debug("break", " ---> aw is " .. tostring(self.activeWidth))
      end
      self:considerDemerits(pi, breakType)
      if debugging then
        SU.debug("break", " <--- cuaw is " .. tostring(self.curActiveWidth))
        SU.debug("break", " <--- aw is " .. tostring(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()
  local shortfall = self.lineWidth - self.curActiveWidth
  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, shortfall)
  end
  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.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.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 = " .. tostring(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.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 = " .. tostring(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)
  --print("@@" .. 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.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