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

sile-typesetter / sile / 9432254829

08 Jun 2024 11:20PM UTC coverage: 60.675% (+3.5%) from 57.223%
9432254829

push

github

alerque
fix(build): Bundle all assets in source distribution

...even when configured for doing a static binary build with embedded
assets. They don't get installed, but they should be in the dist file in
case the good folks building want to configure it a different way.

10441 of 17208 relevant lines covered (60.68%)

1913.98 hits per line

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

71.94
/core/break.lua
1
SILE.settings:declare({
90✔
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 })
90✔
8
SILE.settings:declare({ parameter = "linebreak.pretolerance", type = "integer or nil", default = 100 })
90✔
9
SILE.settings:declare({ parameter = "linebreak.hangIndent", type = "measurement", default = 0 })
90✔
10
SILE.settings:declare({ parameter = "linebreak.hangAfter", type = "integer or nil", default = nil })
90✔
11
SILE.settings:declare({
90✔
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 })
90✔
18
SILE.settings:declare({ parameter = "linebreak.prevGraf", type = "integer", default = 0 })
90✔
19
SILE.settings:declare({ parameter = "linebreak.emergencyStretch", type = "measurement", default = 0 })
90✔
20
SILE.settings:declare({ parameter = "linebreak.doLastLineFit", type = "boolean", default = false }) -- unimplemented
90✔
21
SILE.settings:declare({ parameter = "linebreak.linePenalty", type = "integer", default = 10 })
90✔
22
SILE.settings:declare({ parameter = "linebreak.hyphenPenalty", type = "integer", default = 50 })
90✔
23
SILE.settings:declare({ parameter = "linebreak.doubleHyphenDemerits", type = "integer", default = 10000 })
90✔
24
SILE.settings:declare({ parameter = "linebreak.finalHyphenDemerits", type = "integer", default = 5000 })
90✔
25

26
-- doubleHyphenDemerits
27
-- hyphenPenalty
28

29
local classes = { "tight", "decent", "loose", "veryLoose" }
90✔
30
local passSerial = 0
90✔
31
local awful_bad = 1073741823
90✔
32
local inf_bad = 10000
90✔
33
local ejectPenalty = -inf_bad
90✔
34
local lineBreak = {}
90✔
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)
5,294✔
52
   return type(value) == "table" and value:absolute() or value
5,573✔
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
90✔
58

59
function lineBreak:init ()
90✔
60
   self:trimGlue() -- 842
269✔
61
   -- 849
62
   self.activeWidth = SILE.types.length()
538✔
63
   self.curActiveWidth = SILE.types.length()
538✔
64
   self.breakWidth = SILE.types.length()
538✔
65
   -- 853
66
   local rskip = (SILE.settings:get("document.rskip") or SILE.types.node.glue()).width:absolute()
723✔
67
   local lskip = (SILE.settings:get("document.lskip") or SILE.types.node.glue()).width:absolute()
724✔
68
   self.background = rskip + lskip
538✔
69
   -- 860
70
   self.bestInClass = {}
269✔
71
   for i = 1, #classes do
1,345✔
72
      self.bestInClass[classes[i]] = {
1,076✔
73
         minimalDemerits = awful_bad,
1,076✔
74
      }
1,076✔
75
   end
76
   self.minimumDemerits = awful_bad
269✔
77
   self:setupLineLengths()
269✔
78
end
79

80
function lineBreak:trimGlue () -- 842
90✔
81
   local nodes = self.nodes
269✔
82
   if nodes[#nodes].is_glue then
269✔
83
      nodes[#nodes] = nil
×
84
   end
85
   nodes[#nodes + 1] = SILE.types.node.penalty(inf_bad)
538✔
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 (_)
90✔
107
   return 0, self.hsize, 0
×
108
end
109

110
local parShapeCache = {}
90✔
111

112
local grantLeftoverWidth = function (hsize, l, w, r)
113
   local width = SILE.types.measurement(w or hsize)
133✔
114
   if not w and l then
133✔
115
      width = width - SILE.types.measurement(l)
32✔
116
   end
117
   if not w and r then
133✔
118
      width = width - SILE.types.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)
90✔
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 (_)
90✔
138
   pl.tablex.clear(parShapeCache)
269✔
139
end
140

141
function lineBreak:setupLineLengths () -- 874
90✔
142
   self.parShaping = param("parShape") or false
538✔
143
   if self.parShaping then
269✔
144
      self.lastSpecialLine = nil
2✔
145
      self.easy_line = nil
2✔
146
   else
147
      self.hangAfter = param("hangAfter") or 0
534✔
148
      self.hangIndent = param("hangIndent"):tonumber()
801✔
149
      if self.hangIndent == 0 then
267✔
150
         self.lastSpecialLine = 0
267✔
151
         self.secondWidth = self.hsize or SU.error("No hsize")
267✔
152
      else -- 875
153
         self.lastSpecialLine = math.abs(self.hangAfter)
×
154
         if self.hangAfter < 0 then
×
155
            self.secondWidth = self.hsize or SU.error("No hsize")
×
156
            self.firstWidth = self.hsize - math.abs(self.hangIndent)
×
157
         else
158
            self.firstWidth = self.hsize or SU.error("No hsize")
×
159
            self.secondWidth = self.hsize - math.abs(self.hangIndent)
×
160
         end
161
      end
162
      if param("looseness") == 0 then
534✔
163
         self.easy_line = self.lastSpecialLine
267✔
164
      else
165
         self.easy_line = awful_bad
×
166
      end
167
      -- self.easy_line = awful_bad
168
   end
169
end
170

171
function lineBreak:tryBreak () -- 855
90✔
172
   local pi, breakType
173
   local node = self.nodes[self.place]
3,622✔
174
   if not node then
3,622✔
175
      pi = ejectPenalty
×
176
      breakType = "hyphenated"
×
177
   elseif node.is_discretionary then
3,622✔
178
      breakType = "hyphenated"
517✔
179
      pi = param("hyphenPenalty")
1,034✔
180
   else
181
      breakType = "unhyphenated"
3,105✔
182
      pi = node.penalty or 0
3,105✔
183
   end
184
   if debugging then
3,622✔
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
3,622✔
188
   self.prev_prev_r = nil
3,622✔
189
   self.prev_r = self.activeListHead
3,622✔
190
   self.old_l = 0
3,622✔
191
   self.r = nil
3,622✔
192
   self.curActiveWidth = SILE.types.length(self.activeWidth)
7,244✔
193
   while true do
194
      while true do -- allows "break" to function as "continue"
195
         self.r = self.prev_r.next
14,449✔
196
         if debugging then
14,449✔
197
            SU.debug(
×
198
               "break",
199
               "We have moved the link  forward, ln is now",
200
               self.r.type == "delta" and "XX" or self.r.lineNumber
×
201
            )
202
         end
203
         if self.r.type == "delta" then -- 858
14,449✔
204
            if debugging then
3,183✔
205
               SU.debug("break", " Adding delta node width of", self.r.width)
×
206
            end
207
            self.curActiveWidth:___add(self.r.width)
3,183✔
208
            self.prev_prev_r = self.prev_r
3,183✔
209
            self.prev_r = self.r
3,183✔
210
            break
3,183✔
211
         end
212
         -- 861
213
         if self.r.lineNumber > self.old_l then
11,266✔
214
            if debugging then
7,254✔
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
7,254✔
218
               self:createNewActiveNodes(breakType)
919✔
219
            end
220
            if self.r == self.activeListHead then
7,254✔
221
               if debugging then
3,622✔
222
                  SU.debug("break", "<- tryBreak")
×
223
               end
224
               return
3,622✔
225
            end
226
            -- 876
227
            if self.easy_line and self.r.lineNumber > self.easy_line then
3,632✔
228
               self.lineWidth = self.secondWidth
3,514✔
229
               self.old_l = awful_bad - 1
3,514✔
230
            else
231
               self.old_l = self.r.lineNumber
118✔
232
               if self.lastSpecialLine and self.r.lineNumber > self.lastSpecialLine then
118✔
233
                  self.lineWidth = self.secondWidth
×
234
               elseif self.parShaping then
118✔
235
                  local _
236
                  _, self.lineWidth, _ = self:parShapeCache(self.r.lineNumber)
236✔
237
               else
238
                  self.lineWidth = self.firstWidth
×
239
               end
240
            end
241
            if debugging then
3,632✔
242
               SU.debug("break", "line width =", self.lineWidth)
×
243
            end
244
         end
245
         if debugging then
7,644✔
246
            SU.debug("break", " ---> (2) cuaw is", self.curActiveWidth)
×
247
            SU.debug("break", " ---> aw is", self.activeWidth)
×
248
         end
249
         self:considerDemerits(pi, breakType)
7,644✔
250
         if debugging then
7,644✔
251
            SU.debug("break", " <--- cuaw is", self.curActiveWidth)
×
252
            SU.debug("break", " <--- aw is ", 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
7,644✔
263
   local badness, class
264
   local stretch = self.curActiveWidth.stretch.amount
7,644✔
265
   local shrink = self.curActiveWidth.shrink.amount
7,644✔
266
   if shortfall > 0 then
7,644✔
267
      if shortfall > 110 and stretch < 25 then
7,121✔
268
         badness = inf_bad
3,116✔
269
      else
270
         badness = SU.rateBadness(inf_bad, shortfall, stretch)
8,010✔
271
      end
272
      if badness > 99 then
7,121✔
273
         class = "veryLoose"
4,826✔
274
      elseif badness > 12 then
2,295✔
275
         class = "loose"
66✔
276
      else
277
         class = "decent"
2,229✔
278
      end
279
   else
280
      shortfall = -shortfall
523✔
281
      if shortfall > shrink then
523✔
282
         badness = inf_bad + 1
369✔
283
      else
284
         badness = SU.rateBadness(inf_bad, shortfall, shrink)
308✔
285
      end
286
      if badness > 12 then
523✔
287
         class = "tight"
427✔
288
      else
289
         class = "decent"
96✔
290
      end
291
   end
292
   return badness, class
7,644✔
293
end
294

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

346
function lineBreak:considerDemerits (pi, breakType) -- 877
90✔
347
   self.artificialDemerits = false
7,644✔
348
   local nodeStaysActive = false
7,644✔
349
   -- self:dumpActiveRing()
350
   if self.seenAlternatives then
7,644✔
351
      self:tryAlternatives(
×
352
         self.r.prevBreak and self.r.prevBreak.curBreak or 1,
×
353
         self.r.curBreak and self.r.curBreak or 1
×
354
      )
355
   end
356
   local shortfall = self.lineWidth - self.curActiveWidth
7,644✔
357
   self.badness, self.fitClass = fitclass(self, shortfall)
15,288✔
358
   if debugging then
7,644✔
359
      SU.debug("break", self.badness, self.fitClass)
×
360
   end
361
   if self.badness > inf_bad or pi == ejectPenalty then
7,644✔
362
      if
363
         self.finalpass
364
         and self.minimumDemerits == awful_bad
898✔
365
         and self.r.next == self.activeListHead
46✔
366
         and self.prev_r == self.activeListHead
27✔
367
      then
368
         self.artificialDemerits = true
27✔
369
      else
370
         if self.badness > self.threshold then
871✔
371
            self:deactivateR()
355✔
372
            return
355✔
373
         end
374
      end
375
   else
376
      self.prev_r = self.r
6,746✔
377
      if self.badness > self.threshold then
6,746✔
378
         return
4,749✔
379
      end
380
      nodeStaysActive = true
1,997✔
381
   end
382

383
   local _shortfall = shortfall:tonumber()
2,540✔
384
   local function shortfallratio (metric)
385
      local prop = self.curActiveWidth[metric]:tonumber()
2,540✔
386
      local factor = prop ~= 0 and prop or awful_bad
2,540✔
387
      return _shortfall / factor
2,540✔
388
   end
389
   self.lastRatio = shortfallratio(_shortfall > 0 and "stretch" or "shrink")
5,080✔
390
   self:recordFeasible(pi, breakType)
2,540✔
391
   if not nodeStaysActive then
2,540✔
392
      self:deactivateR()
543✔
393
   end
394
end
395

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

434
function lineBreak:computeDemerits (pi, breakType)
90✔
435
   if self.artificialDemerits then
2,540✔
436
      return 0
27✔
437
   end
438
   local demerit = param("linePenalty") + self.badness
5,026✔
439
   if math.abs(demerit) >= 10000 then
2,513✔
440
      demerit = 100000000
×
441
   else
442
      demerit = demerit * demerit
2,513✔
443
   end
444
   if pi > 0 then
2,513✔
445
      demerit = demerit + pi * pi
188✔
446
   -- elseif pi == 0 then
447
   --   -- do nothing
448
   elseif pi > ejectPenalty then
2,325✔
449
      demerit = demerit - pi * pi
1,809✔
450
   end
451
   if breakType == "hyphenated" and self.r.type == "hyphenated" then
2,513✔
452
      if self.nodes[self.place] then
48✔
453
         demerit = demerit + param("doubleHyphenDemerits")
96✔
454
      else
455
         demerit = demerit + param("finalHyphenDemerits")
×
456
      end
457
   end
458
   -- XXX adjDemerits not added here
459
   return demerit
2,513✔
460
end
461

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

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

542
   for i = 1, #classes do
4,595✔
543
      local class = classes[i]
3,676✔
544
      local best = self.bestInClass[class]
3,676✔
545
      local value = best.minimalDemerits
3,676✔
546
      if debugging then
3,676✔
547
         SU.debug("break", "Class is", class, "Best value here is", value)
×
548
      end
549

550
      if value <= self.minimumDemerits then
3,676✔
551
         -- 871: this is what creates new active notes
552
         passSerial = passSerial + 1
943✔
553

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

573
   self.minimumDemerits = awful_bad
919✔
574
   -- 870
575
   if self.r ~= self.activeListHead then
919✔
576
      local newDelta = { next = self.r, type = "delta", width = self.curActiveWidth - self.breakWidth }
6✔
577
      self.prev_r.next = newDelta
3✔
578
      self.prev_prev_r = self.prev_r
3✔
579
      self.prev_r = newDelta
3✔
580
   end
581
end
582

583
function lineBreak.dumpBreakNode (_, node)
90✔
584
   if not SU.debugging("break") then
1,886✔
585
      return
943✔
586
   end
587
   SU.debug("break", lineBreak:describeBreakNode(node))
×
588
end
589

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

612
-- NOTE: this function is called many thousands of times even in single
613
-- page documents. Speed is more important than pretty code here.
614
function lineBreak:checkForLegalBreak (node) -- 892
90✔
615
   if debugging then
7,238✔
616
      SU.debug("break", "considering node " .. node)
×
617
   end
618
   local previous = self.nodes[self.place - 1]
7,238✔
619
   if node.is_alternative then
7,238✔
620
      self.seenAlternatives = true
×
621
   end
622
   if self.sideways and node.is_box then
7,238✔
623
      self.activeWidth:___add(node.height)
×
624
      self.activeWidth:___add(node.depth)
×
625
   elseif self.sideways and node.is_vglue then
7,238✔
626
      if previous and previous.is_box then
×
627
         self:tryBreak()
×
628
      end
629
      self.activeWidth:___add(node.height)
×
630
      self.activeWidth:___add(node.depth)
×
631
   elseif node.is_alternative then
7,238✔
632
      self.activeWidth:___add(node:minWidth())
×
633
   elseif node.is_box then
7,238✔
634
      self.activeWidth:___add(node:lineContribution())
10,431✔
635
   elseif node.is_glue then
3,761✔
636
      -- 894 (We removed the auto_breaking parameter)
637
      if previous and previous.is_box then
2,605✔
638
         self:tryBreak()
2,544✔
639
      end
640
      self.activeWidth:___add(node.width)
5,210✔
641
   elseif node.is_kern then
1,156✔
642
      self.activeWidth:___add(node.width)
152✔
643
   elseif node.is_discretionary then -- 895
1,080✔
644
      self.activeWidth:___add(node:prebreakWidth())
1,034✔
645
      self:tryBreak()
517✔
646
      self.activeWidth:___sub(node:prebreakWidth())
1,034✔
647
      self.activeWidth:___add(node:replacementWidth())
1,551✔
648
   elseif node.is_penalty then
563✔
649
      self:tryBreak()
561✔
650
   end
651
end
652

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

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

731
      -- Not doing 1630
732
      self.activeWidth = SILE.types.length(self.background)
608✔
733

734
      self.place = 1
304✔
735
      while self.nodes[self.place] and self.activeListHead.next ~= self.activeListHead do
7,542✔
736
         self:checkForLegalBreak(self.nodes[self.place])
7,238✔
737
         self.place = self.place + 1
7,238✔
738
      end
739
      if self.place > #self.nodes then
304✔
740
         if self:tryFinalBreak() then
538✔
741
            break
269✔
742
         end
743
      end
744
      -- (Not doing 891)
745
      if self.pass ~= "second" then
35✔
746
         self.pass = "second"
23✔
747
         self.threshold = param("tolerance")
46✔
748
      else
749
         self.pass = "emergency"
12✔
750
         self.background.stretch:___add(param("emergencyStretch"))
24✔
751
         self.finalpass = true
12✔
752
      end
753
   end
754
   -- Not doing 1638
755
   return self:postLineBreak()
269✔
756
end
757

758
function lineBreak:postLineBreak () -- 903
90✔
759
   local p = self.bestBet
269✔
760
   local breaks = {}
269✔
761
   local line = 1
269✔
762

763
   local nbLines = 0
269✔
764
   local p2 = p
269✔
765
   repeat
766
      nbLines = nbLines + 1
399✔
767
      p2 = p2.prevBreak
399✔
768
   until not p2
399✔
769

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

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

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

839
return lineBreak
90✔
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

© 2025 Coveralls, Inc