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

sile-typesetter / sile / 9304060604

30 May 2024 02:07PM UTC coverage: 74.124% (-0.6%) from 74.707%
9304060604

push

github

alerque
style: Reformat Lua with stylua

8104 of 11995 new or added lines in 184 files covered. (67.56%)

15 existing lines in 11 files now uncovered.

12444 of 16788 relevant lines covered (74.12%)

7175.1 hits per line

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

75.99
/core/break.lua
1
SILE.settings:declare({
181✔
2
   parameter = "linebreak.parShape",
3
   type = "boolean",
4
   default = false,
5
   help = "If set to true, the paragraph shaping method is activated.",
6
})
7
SILE.settings:declare({ parameter = "linebreak.tolerance", type = "integer or nil", default = 500 })
181✔
8
SILE.settings:declare({ parameter = "linebreak.pretolerance", type = "integer or nil", default = 100 })
181✔
9
SILE.settings:declare({ parameter = "linebreak.hangIndent", type = "measurement", default = 0 })
181✔
10
SILE.settings:declare({ parameter = "linebreak.hangAfter", type = "integer or nil", default = nil })
181✔
11
SILE.settings:declare({
181✔
12
   parameter = "linebreak.adjdemerits",
13
   type = "integer",
14
   default = 10000,
15
   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.",
16
})
17
SILE.settings:declare({ parameter = "linebreak.looseness", type = "integer", default = 0 })
181✔
18
SILE.settings:declare({ parameter = "linebreak.prevGraf", type = "integer", default = 0 })
181✔
19
SILE.settings:declare({ parameter = "linebreak.emergencyStretch", type = "measurement", default = 0 })
181✔
20
SILE.settings:declare({ parameter = "linebreak.doLastLineFit", type = "boolean", default = false }) -- unimplemented
181✔
21
SILE.settings:declare({ parameter = "linebreak.linePenalty", type = "integer", default = 10 })
181✔
22
SILE.settings:declare({ parameter = "linebreak.hyphenPenalty", type = "integer", default = 50 })
181✔
23
SILE.settings:declare({ parameter = "linebreak.doubleHyphenDemerits", type = "integer", default = 10000 })
181✔
24
SILE.settings:declare({ parameter = "linebreak.finalHyphenDemerits", type = "integer", default = 5000 })
181✔
25

26
-- doubleHyphenDemerits
27
-- hyphenPenalty
28

29
local classes = { "tight", "decent", "loose", "veryLoose" }
181✔
30
local passSerial = 0
181✔
31
local awful_bad = 1073741823
181✔
32
local inf_bad = 10000
181✔
33
local ejectPenalty = -inf_bad
181✔
34
local lineBreak = {}
181✔
35

36
--[[
37
  Basic control flow:
38
  doBreak:
39
    init
40
    for each node:
41
      checkForLegalBreak
42
        tryBreak
43
          createNewActiveNodes
44
          considerDemerits
45
            deactivateR (or) recordFeasible
46
    tryFinalBreak
47
    postLineBreak
48
]]
49

50
local param = function (key)
51
   local value = SILE.settings:get("linebreak." .. key)
17,604✔
52
   return type(value) == "table" and value:absolute() or value
18,516✔
53
end
54

55
-- Routines here will be called thousands of times; we micro-optimize
56
-- to avoid debugging and concat calls.
57
local debugging = false
181✔
58

59
function lineBreak:init ()
181✔
60
   self:trimGlue() -- 842
856✔
61
   -- 849
62
   self.activeWidth = SILE.length()
1,712✔
63
   self.curActiveWidth = SILE.length()
1,712✔
64
   self.breakWidth = SILE.length()
1,712✔
65
   -- 853
66
   local rskip = (SILE.settings:get("document.rskip") or SILE.nodefactory.glue()).width:absolute()
2,404✔
67
   local lskip = (SILE.settings:get("document.lskip") or SILE.nodefactory.glue()).width:absolute()
2,386✔
68
   self.background = rskip + lskip
1,712✔
69
   -- 860
70
   self.bestInClass = {}
856✔
71
   for i = 1, #classes do
4,280✔
72
      self.bestInClass[classes[i]] = {
3,424✔
73
         minimalDemerits = awful_bad,
3,424✔
74
      }
3,424✔
75
   end
76
   self.minimumDemerits = awful_bad
856✔
77
   self:setupLineLengths()
856✔
78
end
79

80
function lineBreak:trimGlue () -- 842
181✔
81
   local nodes = self.nodes
856✔
82
   if nodes[#nodes].is_glue then
856✔
NEW
83
      nodes[#nodes] = nil
×
84
   end
85
   nodes[#nodes + 1] = SILE.nodefactory.penalty(inf_bad)
1,712✔
86
end
87

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

110
local parShapeCache = {}
181✔
111

112
local grantLeftoverWidth = function (hsize, l, w, r)
113
   local width = SILE.measurement(w or hsize)
133✔
114
   if not w and l then
133✔
115
      width = width - SILE.measurement(l)
32✔
116
   end
117
   if not w and r then
133✔
118
      width = width - SILE.measurement(r)
32✔
119
   end
120
   local remaining = hsize:tonumber() - width:tonumber()
399✔
121
   local left = SU.cast("number", l or (r and (remaining - SU.cast("number", r))) or 0)
139✔
122
   local right = SU.cast("number", r or (l and (remaining - SU.cast("number", l))) or remaining)
169✔
123
   return left, width, right
133✔
124
end
125

126
-- Wrap linebreak:parShape in a memoized table for fast access
127
function lineBreak:parShapeCache (n)
181✔
128
   local cache = parShapeCache[n]
133✔
129
   if not cache then
133✔
130
      local l, w, r = self:parShape(n)
133✔
131
      local left, width, right = grantLeftoverWidth(self.hsize, l, w, r)
133✔
132
      cache = { left, width, right }
133✔
133
   end
134
   return cache[1], cache[2], cache[3]
133✔
135
end
136

137
function lineBreak.parShapeCacheClear (_)
181✔
138
   pl.tablex.clear(parShapeCache)
856✔
139
end
140

141
function lineBreak:setupLineLengths () -- 874
181✔
142
   self.parShaping = param("parShape") or false
1,712✔
143
   if self.parShaping then
856✔
144
      self.lastSpecialLine = nil
2✔
145
      self.easy_line = nil
2✔
146
   else
147
      self.hangAfter = param("hangAfter") or 0
1,708✔
148
      self.hangIndent = param("hangIndent"):tonumber()
2,562✔
149
      if self.hangIndent == 0 then
854✔
150
         self.lastSpecialLine = 0
843✔
151
         self.secondWidth = self.hsize or SU.error("No hsize")
843✔
152
      else -- 875
153
         self.lastSpecialLine = math.abs(self.hangAfter)
11✔
154
         if self.hangAfter < 0 then
11✔
155
            self.secondWidth = self.hsize or SU.error("No hsize")
9✔
156
            self.firstWidth = self.hsize - math.abs(self.hangIndent)
18✔
157
         else
158
            self.firstWidth = self.hsize or SU.error("No hsize")
2✔
159
            self.secondWidth = self.hsize - math.abs(self.hangIndent)
4✔
160
         end
161
      end
162
      if param("looseness") == 0 then
1,708✔
163
         self.easy_line = self.lastSpecialLine
854✔
164
      else
NEW
165
         self.easy_line = awful_bad
×
166
      end
167
      -- self.easy_line = awful_bad
168
   end
169
end
170

171
function lineBreak:tryBreak () -- 855
181✔
172
   local pi, breakType
173
   local node = self.nodes[self.place]
15,263✔
174
   if not node then
15,263✔
NEW
175
      pi = ejectPenalty
×
NEW
176
      breakType = "hyphenated"
×
177
   elseif node.is_discretionary then
15,263✔
178
      breakType = "hyphenated"
3,327✔
179
      pi = param("hyphenPenalty")
6,654✔
180
   else
181
      breakType = "unhyphenated"
11,936✔
182
      pi = node.penalty or 0
11,936✔
183
   end
184
   if debugging then
15,263✔
NEW
185
      SU.debug("break", "Trying a ", breakType, "break p =", pi)
×
186
   end
187
   self.no_break_yet = true -- We have to store all this state crap in the object, or it's global variables all the way
15,263✔
188
   self.prev_prev_r = nil
15,263✔
189
   self.prev_r = self.activeListHead
15,263✔
190
   self.old_l = 0
15,263✔
191
   self.r = nil
15,263✔
192
   self.curActiveWidth = SILE.length(self.activeWidth)
30,526✔
193
   while true do
194
      while true do -- allows "break" to function as "continue"
195
         self.r = self.prev_r.next
53,617✔
196
         if debugging then
53,617✔
NEW
197
            SU.debug(
×
198
               "break",
199
               "We have moved the link  forward, ln is now",
NEW
200
               self.r.type == "delta" and "XX" or self.r.lineNumber
×
201
            )
202
         end
203
         if self.r.type == "delta" then -- 858
53,617✔
204
            if debugging then
10,030✔
NEW
205
               SU.debug("break", " Adding delta node width of", self.r.width)
×
206
            end
207
            self.curActiveWidth:___add(self.r.width)
10,030✔
208
            self.prev_prev_r = self.prev_r
10,030✔
209
            self.prev_r = self.r
10,030✔
210
            break
10,030✔
211
         end
212
         -- 861
213
         if self.r.lineNumber > self.old_l then
43,587✔
214
            if debugging then
30,591✔
NEW
215
               SU.debug("break", "Minimum demerits = " .. self.minimumDemerits)
×
216
            end
217
            if self.minimumDemerits < awful_bad and (self.old_l ~= self.easy_line or self.r == self.activeListHead) then
30,591✔
218
               self:createNewActiveNodes(breakType)
2,900✔
219
            end
220
            if self.r == self.activeListHead then
30,591✔
221
               if debugging then
15,263✔
NEW
222
                  SU.debug("break", "<- tryBreak")
×
223
               end
224
               return
15,263✔
225
            end
226
            -- 876
227
            if self.easy_line and self.r.lineNumber > self.easy_line then
15,328✔
228
               self.lineWidth = self.secondWidth
14,659✔
229
               self.old_l = awful_bad - 1
14,659✔
230
            else
231
               self.old_l = self.r.lineNumber
669✔
232
               if self.lastSpecialLine and self.r.lineNumber > self.lastSpecialLine then
669✔
NEW
233
                  self.lineWidth = self.secondWidth
×
234
               elseif self.parShaping then
669✔
235
                  local _
236
                  _, self.lineWidth, _ = self:parShapeCache(self.r.lineNumber)
236✔
237
               else
238
                  self.lineWidth = self.firstWidth
551✔
239
               end
240
            end
241
            if debugging then
15,328✔
NEW
242
               SU.debug("break", "line width = " .. tostring(self.lineWidth))
×
243
            end
244
         end
245
         if debugging then
28,324✔
NEW
246
            SU.debug("break", " ---> (2) cuaw is " .. tostring(self.curActiveWidth))
×
NEW
247
            SU.debug("break", " ---> aw is " .. tostring(self.activeWidth))
×
248
         end
249
         self:considerDemerits(pi, breakType)
28,324✔
250
         if debugging then
28,324✔
NEW
251
            SU.debug("break", " <--- cuaw is " .. tostring(self.curActiveWidth))
×
NEW
252
            SU.debug("break", " <--- aw is " .. tostring(self.activeWidth))
×
253
         end
254
      end
255
   end
256
end
257

258
-- Note: This function gets called a lot and to optimize it we're assuming that
259
-- the lengths being passed are already absolutized. This is not a safe
260
-- assumption to make universally.
261
local function fitclass (self, shortfall)
262
   shortfall = shortfall.amount
28,324✔
263
   local badness, class
264
   local stretch = self.curActiveWidth.stretch.amount
28,324✔
265
   local shrink = self.curActiveWidth.shrink.amount
28,324✔
266
   if shortfall > 0 then
28,324✔
267
      if shortfall > 110 and stretch < 25 then
26,397✔
268
         badness = inf_bad
13,188✔
269
      else
270
         badness = SU.rateBadness(inf_bad, shortfall, stretch)
26,418✔
271
      end
272
      if badness > 99 then
26,397✔
273
         class = "veryLoose"
20,380✔
274
      elseif badness > 12 then
6,017✔
275
         class = "loose"
295✔
276
      else
277
         class = "decent"
5,722✔
278
      end
279
   else
280
      shortfall = -shortfall
1,927✔
281
      if shortfall > shrink then
1,927✔
282
         badness = inf_bad + 1
1,407✔
283
      else
284
         badness = SU.rateBadness(inf_bad, shortfall, shrink)
1,040✔
285
      end
286
      if badness > 12 then
1,927✔
287
         class = "tight"
1,654✔
288
      else
289
         class = "decent"
273✔
290
      end
291
   end
292
   return badness, class
28,324✔
293
end
294

295
function lineBreak:tryAlternatives (from, to)
181✔
NEW
296
   local altSizes = {}
×
NEW
297
   local alternates = {}
×
NEW
298
   for i = from, to do
×
NEW
299
      if self.nodes[i] and self.nodes[i].is_alternative then
×
NEW
300
         alternates[#alternates + 1] = self.nodes[i]
×
NEW
301
         altSizes[#altSizes + 1] = #self.nodes[i].options
×
302
      end
303
   end
NEW
304
   if #alternates == 0 then
×
NEW
305
      return
×
306
   end
NEW
307
   local localMinimum = awful_bad
×
308
   -- local selectedShortfall
NEW
309
   local shortfall = self.lineWidth - self.curActiveWidth
×
NEW
310
   if debugging then
×
NEW
311
      SU.debug("break", "Shortfall was ", shortfall)
×
312
   end
NEW
313
   for combination in SU.allCombinations(altSizes) do
×
NEW
314
      local addWidth = 0
×
NEW
315
      for i = 1, #alternates do
×
NEW
316
         local alternative = alternates[i]
×
NEW
317
         addWidth = (addWidth + alternative.options[combination[i]].width - alternative:minWidth())
×
NEW
318
         if debugging then
×
NEW
319
            SU.debug("break", alternative.options[combination[i]], " width", addWidth)
×
320
         end
321
      end
NEW
322
      local ss = shortfall - addWidth
×
323
      -- Warning, assumes abosolute
324
      local badness =
NEW
325
         SU.rateBadness(inf_bad, ss.length.amount, self.curActiveWidth[ss > 0 and "stretch" or "shrink"].length.amount)
×
NEW
326
      if debugging then
×
NEW
327
         SU.debug("break", "  badness of " .. ss .. " (" .. self.curActiveWidth .. ") is " .. badness)
×
328
      end
NEW
329
      if badness < localMinimum then
×
NEW
330
         self.r.alternates = alternates
×
NEW
331
         self.r.altSelections = combination
×
332
         -- selectedShortfall = addWidth
NEW
333
         localMinimum = badness
×
334
      end
335
   end
NEW
336
   if debugging then
×
NEW
337
      SU.debug("break", "Choosing ", alternates[1].options[self.r.altSelections[1]])
×
338
   end
339
   -- self.curActiveWidth:___add(selectedShortfall)
NEW
340
   shortfall = self.lineWidth - self.curActiveWidth
×
NEW
341
   if debugging then
×
NEW
342
      SU.debug("break", "Is now ", shortfall)
×
343
   end
344
end
345

346
function lineBreak:considerDemerits (pi, breakType) -- 877
181✔
347
   self.artificialDemerits = false
28,324✔
348
   local nodeStaysActive = false
28,324✔
349
   -- self:dumpActiveRing()
350
   local shortfall = self.lineWidth - self.curActiveWidth
28,324✔
351
   if self.seenAlternatives then
28,324✔
NEW
352
      self:tryAlternatives(
×
NEW
353
         self.r.prevBreak and self.r.prevBreak.curBreak or 1,
×
NEW
354
         self.r.curBreak and self.r.curBreak or 1,
×
355
         shortfall
356
      )
357
   end
358
   shortfall = self.lineWidth - self.curActiveWidth
28,324✔
359
   self.badness, self.fitClass = fitclass(self, shortfall)
56,648✔
360
   if debugging then
28,324✔
NEW
361
      SU.debug("break", self.badness .. " " .. self.fitClass)
×
362
   end
363
   if self.badness > inf_bad or pi == ejectPenalty then
28,324✔
364
      if
365
         self.finalpass
366
         and self.minimumDemerits == awful_bad
3,019✔
367
         and self.r.next == self.activeListHead
288✔
368
         and self.prev_r == self.activeListHead
157✔
369
      then
370
         self.artificialDemerits = true
157✔
371
      else
372
         if self.badness > self.threshold then
2,862✔
373
            self:deactivateR()
1,399✔
374
            return
1,399✔
375
         end
376
      end
377
   else
378
      self.prev_r = self.r
25,305✔
379
      if self.badness > self.threshold then
25,305✔
380
         return
19,915✔
381
      end
382
      nodeStaysActive = true
5,390✔
383
   end
384

385
   local _shortfall = shortfall:tonumber()
7,010✔
386
   local function shortfallratio (metric)
387
      local prop = self.curActiveWidth[metric]:tonumber()
7,010✔
388
      local factor = prop ~= 0 and prop or awful_bad
7,010✔
389
      return _shortfall / factor
7,010✔
390
   end
391
   self.lastRatio = shortfallratio(_shortfall > 0 and "stretch" or "shrink")
14,020✔
392
   self:recordFeasible(pi, breakType)
7,010✔
393
   if not nodeStaysActive then
7,010✔
394
      self:deactivateR()
1,620✔
395
   end
396
end
397

398
function lineBreak:deactivateR () -- 886
181✔
399
   if debugging then
3,019✔
NEW
400
      SU.debug("break", " Deactivating r (" .. self.r.type .. ")")
×
401
   end
402
   self.prev_r.next = self.r.next
3,019✔
403
   if self.prev_r == self.activeListHead then
3,019✔
404
      -- 887
405
      self.r = self.activeListHead.next
3,012✔
406
      if self.r.type == "delta" then
3,012✔
407
         self.activeWidth:___add(self.r.width)
1,753✔
408
         self.curActiveWidth = SILE.length(self.activeWidth)
3,506✔
409
         self.activeListHead.next = self.r.next
1,753✔
410
      end
411
      if debugging then
3,012✔
NEW
412
         SU.debug("break", "  Deactivate, branch 1")
×
413
      end
414
   else
415
      if self.prev_r.type == "delta" then
7✔
416
         self.r = self.prev_r.next
7✔
417
         if self.r == self.activeListHead then
7✔
NEW
418
            self.curActiveWidth:___sub(self.prev_r.width)
×
419
            -- FIXME It was crashing here, so changed from:
420
            -- self.curActiveWidth:___sub(self.r.width)
421
            -- But I'm not so sure reading Knuth here...
NEW
422
            self.prev_prev_r.next = self.activeListHead
×
NEW
423
            self.prev_r = self.prev_prev_r
×
424
         elseif self.r.type == "delta" then
7✔
425
            self.curActiveWidth:___add(self.r.width)
7✔
426
            self.prev_r.width:___add(self.r.width)
7✔
427
            self.prev_r.next = self.r.next
7✔
428
         end
429
      end
430
      if debugging then
7✔
NEW
431
         SU.debug("break", "  Deactivate, branch 2")
×
432
      end
433
   end
434
end
435

436
function lineBreak:computeDemerits (pi, breakType)
181✔
437
   if self.artificialDemerits then
7,010✔
438
      return 0
157✔
439
   end
440
   local demerit = param("linePenalty") + self.badness
13,706✔
441
   if math.abs(demerit) >= 10000 then
6,853✔
NEW
442
      demerit = 100000000
×
443
   else
444
      demerit = demerit * demerit
6,853✔
445
   end
446
   if pi > 0 then
6,853✔
447
      demerit = demerit + pi * pi
652✔
448
   -- elseif pi == 0 then
449
   --   -- do nothing
450
   elseif pi > ejectPenalty then
6,201✔
451
      demerit = demerit - pi * pi
4,738✔
452
   end
453
   if breakType == "hyphenated" and self.r.type == "hyphenated" then
6,853✔
454
      if self.nodes[self.place] then
214✔
455
         demerit = demerit + param("doubleHyphenDemerits")
428✔
456
      else
NEW
457
         demerit = demerit + param("finalHyphenDemerits")
×
458
      end
459
   end
460
   -- XXX adjDemerits not added here
461
   return demerit
6,853✔
462
end
463

464
function lineBreak:recordFeasible (pi, breakType) -- 881
181✔
465
   local demerit = lineBreak:computeDemerits(pi, breakType)
7,010✔
466
   if debugging then
7,010✔
NEW
467
      if self.nodes[self.place] then
×
NEW
468
         SU.debug(
×
469
            "break",
470
            "@"
NEW
471
               .. self.nodes[self.place]
×
NEW
472
               .. " via @@"
×
NEW
473
               .. (self.r.serial or "0")
×
NEW
474
               .. " badness="
×
NEW
475
               .. self.badness
×
NEW
476
               .. " demerit="
×
NEW
477
               .. demerit
×
478
         ) -- 882
479
      else
NEW
480
         SU.debug("break", "@ \\par via @@")
×
481
      end
NEW
482
      SU.debug("break", " fit class = " .. self.fitClass)
×
483
   end
484
   demerit = demerit + self.r.totalDemerits
7,010✔
485
   if demerit <= self.bestInClass[self.fitClass].minimalDemerits then
7,010✔
486
      self.bestInClass[self.fitClass] = {
4,520✔
487
         minimalDemerits = demerit,
4,520✔
488
         node = self.r.serial and self.r,
4,520✔
489
         line = self.r.lineNumber,
4,520✔
490
      }
4,520✔
491
      -- XXX do last line fit
492
      if demerit < self.minimumDemerits then
4,520✔
493
         self.minimumDemerits = demerit
3,279✔
494
      end
495
   end
496
end
497

498
function lineBreak:createNewActiveNodes (breakType) -- 862
181✔
499
   if self.no_break_yet then
2,900✔
500
      -- 863
501
      self.no_break_yet = false
2,900✔
502
      self.breakWidth = SILE.length(self.background)
5,800✔
503
      local place = self.place
2,900✔
504
      local node = self.nodes[place]
2,900✔
505
      if node and node.is_discretionary then -- 866
2,900✔
506
         self.breakWidth:___add(node:prebreakWidth())
816✔
507
         self.breakWidth:___add(node:postbreakWidth())
816✔
508
         self.breakWidth:___sub(node:replacementWidth())
816✔
509
      end
510
      while self.nodes[place] and not self.nodes[place].is_box do
7,171✔
511
         if self.sideways and self.nodes[place].height then
4,271✔
NEW
512
            self.breakWidth:___sub(self.nodes[place].height)
×
NEW
513
            self.breakWidth:___sub(self.nodes[place].depth)
×
514
         elseif self.nodes[place].width then -- We use the fact that (a) nodes know if they have width and (b) width subtraction is polymorphic
4,271✔
515
            self.breakWidth:___sub(self.nodes[place]:lineContribution())
8,542✔
516
         end
517
         place = place + 1
4,271✔
518
      end
519
      if debugging then
2,900✔
NEW
520
         SU.debug("break", "Value of breakWidth = " .. tostring(self.breakWidth))
×
521
      end
522
   end
523
   -- 869 (Add a new delta node)
524
   if self.prev_r.type == "delta" then
2,900✔
525
      self.prev_r.width:___sub(self.curActiveWidth)
12✔
526
      self.prev_r.width:___add(self.breakWidth)
24✔
527
   elseif self.prev_r == self.activeListHead then
2,888✔
528
      self.activeWidth = SILE.length(self.breakWidth)
1,974✔
529
   else
530
      local newDelta = { next = self.r, type = "delta", width = self.breakWidth - self.curActiveWidth }
3,802✔
531
      if debugging then
1,901✔
NEW
532
         SU.debug("break", "Added new delta node = " .. tostring(newDelta.width))
×
533
      end
534
      self.prev_r.next = newDelta
1,901✔
535
      self.prev_prev_r = self.prev_r
1,901✔
536
      self.prev_r = newDelta
1,901✔
537
   end
538
   if math.abs(self.adjdemerits) >= (awful_bad - self.minimumDemerits) then
2,900✔
NEW
539
      self.minimumDemerits = awful_bad - 1
×
540
   else
541
      self.minimumDemerits = self.minimumDemerits + math.abs(self.adjdemerits)
2,900✔
542
   end
543

544
   for i = 1, #classes do
14,500✔
545
      local class = classes[i]
11,600✔
546
      local best = self.bestInClass[class]
11,600✔
547
      local value = best.minimalDemerits
11,600✔
548
      if debugging then
11,600✔
NEW
549
         SU.debug("break", "Class is " .. class .. " Best value here is " .. value)
×
550
      end
551

552
      if value <= self.minimumDemerits then
11,600✔
553
         -- 871: this is what creates new active notes
554
         passSerial = passSerial + 1
2,992✔
555

556
         local newActive = {
2,992✔
557
            type = breakType,
2,992✔
558
            next = self.r,
2,992✔
559
            curBreak = self.place,
2,992✔
560
            prevBreak = best.node,
2,992✔
561
            serial = passSerial,
2,992✔
562
            ratio = self.lastRatio,
2,992✔
563
            lineNumber = best.line + 1,
2,992✔
564
            fitness = class,
2,992✔
565
            totalDemerits = value,
2,992✔
566
         }
567
         -- DoLastLineFit? 1636 XXX
568
         self.prev_r.next = newActive
2,992✔
569
         self.prev_r = newActive
2,992✔
570
         self:dumpBreakNode(newActive)
2,992✔
571
      end
572
      self.bestInClass[class] = { minimalDemerits = awful_bad }
11,600✔
573
   end
574

575
   self.minimumDemerits = awful_bad
2,900✔
576
   -- 870
577
   if self.r ~= self.activeListHead then
2,900✔
578
      local newDelta = { next = self.r, type = "delta", width = self.curActiveWidth - self.breakWidth }
24✔
579
      self.prev_r.next = newDelta
12✔
580
      self.prev_prev_r = self.prev_r
12✔
581
      self.prev_r = newDelta
12✔
582
   end
583
end
584

585
function lineBreak.dumpBreakNode (_, node)
181✔
586
   if not SU.debugging("break") then
5,984✔
587
      return
2,992✔
588
   end
NEW
589
   SU.debug("break", lineBreak:describeBreakNode(node))
×
590
end
591

592
function lineBreak:describeBreakNode (node)
181✔
593
   --print("@@" .. b.serial .. ": line " .. (b.lineNumber -1) .. "." .. b.fitness .. " " .. b.type .. " t=".. b.totalDemerits .. " -> @@ " .. (b.prevBreak and b.prevBreak.serial or "0") )
NEW
594
   if node.sentinel then
×
NEW
595
      return node.sentinel
×
596
   end
NEW
597
   if node.type == "delta" then
×
NEW
598
      return "delta " .. node.width .. "pt"
×
599
   end
NEW
600
   local before = self.nodes[node.curBreak - 1]
×
NEW
601
   local after = self.nodes[node.curBreak + 1]
×
NEW
602
   local from = node.prevBreak and node.prevBreak.curBreak or 1
×
NEW
603
   local to = node.curBreak
×
NEW
604
   return ('b %s-%s "%s | %s" [%s, %s]'):format(
×
605
      from,
606
      to,
NEW
607
      before and before:toText() or "",
×
NEW
608
      after and after:toText() or "",
×
NEW
609
      node.totalDemerits,
×
610
      node.fitness
611
   )
612
end
613

614
-- NOTE: this function is called many thousands of times even in single
615
-- page documents. Speed is more important than pretty code here.
616
function lineBreak:checkForLegalBreak (node) -- 892
181✔
617
   if debugging then
31,328✔
NEW
618
      SU.debug("break", "considering node " .. node)
×
619
   end
620
   local previous = self.nodes[self.place - 1]
31,328✔
621
   if node.is_alternative then
31,328✔
NEW
622
      self.seenAlternatives = true
×
623
   end
624
   if self.sideways and node.is_box then
31,328✔
NEW
625
      self.activeWidth:___add(node.height)
×
NEW
626
      self.activeWidth:___add(node.depth)
×
627
   elseif self.sideways and node.is_vglue then
31,328✔
NEW
628
      if previous and previous.is_box then
×
NEW
629
         self:tryBreak()
×
630
      end
NEW
631
      self.activeWidth:___add(node.height)
×
NEW
632
      self.activeWidth:___add(node.depth)
×
633
   elseif node.is_alternative then
31,328✔
NEW
634
      self.activeWidth:___add(node:minWidth())
×
635
   elseif node.is_box then
31,328✔
636
      self.activeWidth:___add(node:lineContribution())
46,830✔
637
   elseif node.is_glue then
15,718✔
638
      -- 894 (We removed the auto_breaking parameter)
639
      if previous and previous.is_box then
10,219✔
640
         self:tryBreak()
10,081✔
641
      end
642
      self.activeWidth:___add(node.width)
20,438✔
643
   elseif node.is_kern then
5,499✔
644
      self.activeWidth:___add(node.width)
616✔
645
   elseif node.is_discretionary then -- 895
5,191✔
646
      self.activeWidth:___add(node:prebreakWidth())
6,654✔
647
      self:tryBreak()
3,327✔
648
      self.activeWidth:___sub(node:prebreakWidth())
6,654✔
649
      self.activeWidth:___add(node:replacementWidth())
9,981✔
650
   elseif node.is_penalty then
1,864✔
651
      self:tryBreak()
1,855✔
652
   end
653
end
654

655
function lineBreak:tryFinalBreak () -- 899
181✔
656
   -- XXX TeX has self:tryBreak() here. But this doesn't seem to work
657
   -- for us. If we call tryBreak(), we end up demoting all break points
658
   -- to veryLoose (possibly because the active width gets reset - why?).
659
   -- This means we end up doing unnecessary passes.
660
   -- However, there doesn't seem to be any downside to not calling it
661
   -- (how scary is that?) so I have removed it for now. With this
662
   -- "fix", we only perform hyphenation and emergency passes when necessary
663
   -- instead of every single time. If things go strange with the break
664
   -- algorithm in the future, this should be the first place to look!
665
   -- self:tryBreak()
666
   if self.activeListHead.next == self.activeListHead then
856✔
NEW
667
      return
×
668
   end
669
   self.r = self.activeListHead.next
856✔
670
   local fewestDemerits = awful_bad
856✔
671
   repeat
672
      if self.r.type ~= "delta" and self.r.totalDemerits < fewestDemerits then
1,166✔
673
         fewestDemerits = self.r.totalDemerits
860✔
674
         self.bestBet = self.r
860✔
675
      end
676
      self.r = self.r.next
1,166✔
677
   until self.r == self.activeListHead
1,166✔
678
   if param("looseness") == 0 then
1,712✔
679
      return true
856✔
680
   end
681
   -- XXX node 901 not implemented
NEW
682
   if self.actualLooseness == param("looseness") or self.finalpass then
×
NEW
683
      return true
×
684
   end
685
end
686

687
function lineBreak:doBreak (nodes, hsize, sideways)
181✔
688
   passSerial = 1
856✔
689
   debugging = SILE.debugFlags["break"]
856✔
690
   self.seenAlternatives = false
856✔
691
   self.nodes = nodes
856✔
692
   self.hsize = hsize
856✔
693
   self.sideways = sideways
856✔
694
   self:init()
856✔
695
   self.adjdemerits = param("adjdemerits")
1,712✔
696
   self.threshold = param("pretolerance")
1,712✔
697
   if self.threshold >= 0 then
856✔
698
      self.pass = "first"
856✔
699
      self.finalpass = false
856✔
700
   else
UNCOV
701
      self.threshold = param("tolerance")
×
NEW
702
      self.pass = "second"
×
NEW
703
      self.finalpass = param("emergencyStretch") <= 0
×
704
   end
705
   -- 889
706
   while 1 do
1,040✔
707
      if debugging then
1,040✔
NEW
708
         SU.debug("break", "@" .. self.pass .. "pass")
×
709
      end
710
      if self.threshold > inf_bad then
1,040✔
NEW
711
         self.threshold = inf_bad
×
712
      end
713
      if self.pass == "second" then
1,040✔
714
         self.nodes = SILE.hyphenate(self.nodes)
252✔
715
         SILE.typesetter.state.nodes = self.nodes -- Horrible breaking of separation of concerns here. :-(
126✔
716
      end
717
      -- 890
718
      self.activeListHead = {
1,040✔
719
         sentinel = "START",
720
         type = "hyphenated",
721
         lineNumber = awful_bad,
1,040✔
722
         subtype = 0,
723
      } -- 846
1,040✔
724
      self.activeListHead.next = {
1,040✔
725
         sentinel = "END",
726
         type = "unhyphenated",
727
         fitness = "decent",
728
         next = self.activeListHead,
1,040✔
729
         lineNumber = param("prevGraf") + 1,
2,080✔
730
         totalDemerits = 0,
731
      }
1,040✔
732

733
      -- Not doing 1630
734
      self.activeWidth = SILE.length(self.background)
2,080✔
735

736
      self.place = 1
1,040✔
737
      while self.nodes[self.place] and self.activeListHead.next ~= self.activeListHead do
32,368✔
738
         self:checkForLegalBreak(self.nodes[self.place])
31,328✔
739
         self.place = self.place + 1
31,328✔
740
      end
741
      if self.place > #self.nodes then
1,040✔
742
         if self:tryFinalBreak() then
1,712✔
743
            break
856✔
744
         end
745
      end
746
      -- (Not doing 891)
747
      if self.pass ~= "second" then
184✔
748
         self.pass = "second"
126✔
749
         self.threshold = param("tolerance")
252✔
750
      else
751
         self.pass = "emergency"
58✔
752
         self.background.stretch:___add(param("emergencyStretch"))
116✔
753
         self.finalpass = true
58✔
754
      end
755
   end
756
   -- Not doing 1638
757
   return self:postLineBreak()
856✔
758
end
759

760
function lineBreak:postLineBreak () -- 903
181✔
761
   local p = self.bestBet
856✔
762
   local breaks = {}
856✔
763
   local line = 1
856✔
764

765
   local nbLines = 0
856✔
766
   local p2 = p
856✔
767
   repeat
768
      nbLines = nbLines + 1
1,491✔
769
      p2 = p2.prevBreak
1,491✔
770
   until not p2
1,491✔
771

772
   repeat
773
      local left, _, right
774
      -- SILE handles the actual line width differently than TeX,
775
      -- so below always return a width of self.hsize. Would they
776
      -- be needed at some point, the exact width are commented out
777
      -- below.
778
      if self.parShaping then
1,491✔
779
         left, _, right = self:parShapeCache(nbLines + 1 - line)
30✔
780
      else
781
         if self.hangAfter == 0 then
1,476✔
782
            -- width = self.hsize
783
            left = 0
1,415✔
784
            right = 0
1,415✔
785
         else
786
            local indent
787
            if self.hangAfter > 0 then
61✔
788
               -- width = line > nbLines - self.hangAfter and self.firstWidth or self.secondWidth
789
               indent = line > nbLines - self.hangAfter and 0 or self.hangIndent
16✔
790
            else
791
               -- width = line > nbLines + self.hangAfter and self.firstWidth or self.secondWidth
792
               indent = line > nbLines + self.hangAfter and self.hangIndent or 0
45✔
793
            end
794
            if indent > 0 then
61✔
795
               left = indent
26✔
796
               right = 0
26✔
797
            else
798
               left = 0
35✔
799
               right = -indent
35✔
800
            end
801
         end
802
      end
803

804
      table.insert(breaks, 1, {
2,982✔
805
         position = p.curBreak,
1,491✔
806
         width = self.hsize,
1,491✔
807
         left = left,
1,491✔
808
         right = right,
1,491✔
809
      })
810
      if p.alternates then
1,491✔
NEW
811
         for i = 1, #p.alternates do
×
NEW
812
            p.alternates[i].selected = p.altSelections[i]
×
NEW
813
            p.alternates[i].width = p.alternates[i].options[p.altSelections[i]].width
×
814
         end
815
      end
816
      p = p.prevBreak
1,491✔
817
      line = line + 1
1,491✔
818
   until not p
1,491✔
819
   self:parShapeCacheClear()
856✔
820
   return breaks
856✔
821
end
822

823
function lineBreak:dumpActiveRing ()
181✔
NEW
824
   local p = self.activeListHead
×
NEW
825
   if not SILE.quiet then
×
NEW
826
      io.stderr:write("\n")
×
827
   end
828
   repeat
NEW
829
      if not SILE.quiet then
×
NEW
830
         if p == self.r then
×
NEW
831
            io.stderr:write("-> ")
×
832
         else
NEW
833
            io.stderr:write("   ")
×
834
         end
835
      end
NEW
836
      SU.debug("break", lineBreak:describeBreakNode(p))
×
NEW
837
      p = p.next
×
NEW
838
   until p == self.activeListHead
×
839
end
840

841
return lineBreak
181✔
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