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

sile-typesetter / sile / 10728217744

05 Sep 2024 09:07PM UTC coverage: 58.013% (-4.5%) from 62.464%
10728217744

push

github

alerque
refactor(cli): Satiate clippy's new "never type fallback" lint

New in Rust 1.81.0, scheduled to be an error in edition 2024:

https://github.com/rust-lang/rust/issues/123748

It looks like mlua v0.10 won't need this workaround:

https://github.com/mlua-rs/mlua/commit/3641c9895

10103 of 17415 relevant lines covered (58.01%)

1419.94 hits per line

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

80.93
/packages/bidi/init.lua
1
local base = require("packages.base")
22✔
2

3
local package = pl.class(base)
22✔
4
package._name = "bidi"
22✔
5

6
local icu = require("justenoughicu")
22✔
7

8
local function reverse_portion (tbl, s, e)
9
   local rv = {}
6✔
10
   for i = 1, s - 1 do
66✔
11
      rv[#rv + 1] = tbl[i]
60✔
12
   end
13
   for i = e, s, -1 do
18✔
14
      rv[#rv + 1] = tbl[i]
12✔
15
   end
16
   for i = e + 1, #tbl do
45✔
17
      rv[#rv + 1] = tbl[i]
39✔
18
   end
19
   return rv
6✔
20
end
21

22
local function create_matrix (line, base_level)
23
   -- L2; create a transformation matrix of elements
24
   -- such that output[matrix[i]] = input[i]
25
   -- e.g. No reversions required: [1, 2, 3, 4, 5]
26
   -- Levels [0, 0, 0, 1, 1] -> [1, 2, 3, 5, 4]
27

28
   local max_level = 0
188✔
29
   local matrix = {}
188✔
30
   for i, c in next, line do
3,596✔
31
      if c.level > max_level then
3,408✔
32
         max_level = c.level
6✔
33
      end
34
      matrix[i] = i
3,408✔
35
   end
36

37
   for level = base_level + 1, max_level do
194✔
38
      local level_start
39
      for i, _ in next, line do
117✔
40
         if line[i].level >= level then
111✔
41
            if not level_start then
12✔
42
               level_start = i
6✔
43
            elseif i == #line then
6✔
44
               local level_end = i
×
45
               matrix = reverse_portion(matrix, level_start, level_end)
×
46
               level_start = nil
×
47
            end
48
         else
49
            if level_start then
99✔
50
               local level_end = i - 1
6✔
51
               matrix = reverse_portion(matrix, level_start, level_end)
12✔
52
               level_start = nil
6✔
53
            end
54
         end
55
      end
56
   end
57

58
   return matrix
188✔
59
end
60

61
local function reverse_each_node (nodelist)
62
   for j = 1, #nodelist do
128✔
63
      if nodelist[j].type == "hbox" then
64✔
64
         if nodelist[j].value.items then
64✔
65
            SU.flip_in_place(nodelist[j].value.items)
64✔
66
         end
67
         SU.flip_in_place(nodelist[j].value.glyphString)
64✔
68
      end
69
   end
70
end
71

72
local nodeListToText = function (nl)
73
   local owners, text = {}, {}
125✔
74
   local p = 1
125✔
75
   for i = 1, #nl do
568✔
76
      local n = nl[i]
443✔
77
      if n.text then
443✔
78
         local utfchars = SU.splitUtf8(n.text)
127✔
79
         for j = 1, #utfchars do
4,715✔
80
            owners[p] = { node = n, pos = j }
4,588✔
81
            text[p] = utfchars[j]
4,588✔
82
            p = p + 1
4,588✔
83
         end
84
      else
85
         owners[p] = { node = n }
316✔
86
         text[p] = luautf8.char(0xFFFC)
316✔
87
         p = p + 1
316✔
88
      end
89
   end
90
   return owners, text
125✔
91
end
92

93
local splitNodeAtPos = function (n, splitstart, p)
94
   if n.is_unshaped then
12✔
95
      local utf8chars = SU.splitUtf8(n.text)
12✔
96
      local n2 = SILE.types.node.unshaped({ text = "", options = pl.tablex.copy(n.options) })
24✔
97
      local n1 = SILE.types.node.unshaped({ text = "", options = pl.tablex.copy(n.options) })
24✔
98
      for i = splitstart, #utf8chars do
3,435✔
99
         if i <= p then
3,423✔
100
            n1.text = n1.text .. utf8chars[i]
473✔
101
         else
102
            n2.text = n2.text .. utf8chars[i]
2,950✔
103
         end
104
      end
105
      return n1, n2
12✔
106
   else
107
      SU.error("Unsure how to split node " .. tostring(n) .. " at position " .. p, true)
×
108
   end
109
end
110

111
local splitNodelistIntoBidiRuns = function (typesetter)
112
   local nl = typesetter.state.nodes
125✔
113
   if #nl == 0 then
125✔
114
      return nl
×
115
   end
116
   local owners, text = nodeListToText(nl)
125✔
117
   local base_level = typesetter.frame:writingDirection() == "RTL" and 1 or 0
250✔
118
   local runs = { icu.bidi_runs(table.concat(text), typesetter.frame:writingDirection()) }
250✔
119
   table.sort(runs, function (a, b)
250✔
120
      return a.start < b.start
42✔
121
   end)
122
   -- local newNl = {}
123
   -- Split nodes on run boundaries
124
   for i = 1, #runs do
262✔
125
      local run = runs[i]
137✔
126
      local thisOwner = owners[run.start + run.length]
137✔
127
      local nextOwner = owners[run.start + 1 + run.length]
137✔
128
      -- print(thisOwner, nextOwner)
129
      if nextOwner and thisOwner.node == nextOwner.node then
137✔
130
         local before, after = splitNodeAtPos(nextOwner.node, 1, nextOwner.pos - 1)
12✔
131
         -- print(before, after)
132
         local start = nil
12✔
133
         for j = run.start + 1, run.start + run.length do
487✔
134
            if owners[j].node == nextOwner.node then
475✔
135
               if not start then
473✔
136
                  start = j
12✔
137
               end
138
               owners[j] = { node = before, pos = j - start + 1 }
473✔
139
            end
140
         end
141
         for j = run.start + 1 + run.length, #owners do
2,962✔
142
            if owners[j].node == nextOwner.node then
2,950✔
143
               owners[j] = { node = after, pos = j - (run.start + run.length) }
2,950✔
144
            end
145
         end
146
      end
147
   end
148
   -- Assign direction/level to nodes
149
   for i = 1, #runs do
262✔
150
      local runstart = runs[i].start + 1
137✔
151
      local runend = runstart + runs[i].length - 1
137✔
152
      for j = runstart, runend do
5,041✔
153
         if owners[j].node and owners[j].node.options then
4,904✔
154
            owners[j].node.options.direction = runs[i].dir
4,588✔
155
            owners[j].node.options.bidilevel = runs[i].level - base_level
4,588✔
156
         end
157
      end
158
   end
159
   -- String together nodelist
160
   nl = {}
125✔
161
   for i = 1, #owners do
5,029✔
162
      if #nl and nl[#nl] ~= owners[i].node then
4,904✔
163
         nl[#nl + 1] = owners[i].node
455✔
164
         -- print(nl[#nl], nl[#nl].options)
165
      end
166
   end
167
   -- for i = 1, #nl do print(i, nl[i]) end
168
   return nl
125✔
169
end
170

171
local bidiBoxupNodes = function (typesetter)
172
   local allDone = true
405✔
173
   for i = 1, #typesetter.state.nodes do
848✔
174
      if not typesetter.state.nodes[i].bidiDone then
570✔
175
         allDone = false
443✔
176
      end
177
   end
178
   if allDone then
405✔
179
      return typesetter:nobidi_boxUpNodes()
280✔
180
   end
181
   local newNodeList = splitNodelistIntoBidiRuns(typesetter)
125✔
182
   typesetter:shapeAllNodes(newNodeList)
125✔
183
   typesetter.state.nodes = newNodeList
125✔
184
   local vboxlist = typesetter:nobidi_boxUpNodes()
125✔
185
   -- Scan for out-of-direction material
186
   for i = 1, #vboxlist do
527✔
187
      local v = vboxlist[i]
402✔
188
      if v.is_vbox then
402✔
189
         package.reorder(nil, v, typesetter)
188✔
190
      end
191
   end
192
   return vboxlist
125✔
193
end
194

195
function package.reorder (_, n, typesetter)
22✔
196
   local nl = n.nodes
188✔
197
   -- local newNl = {}
198
   -- local matrix = {}
199
   local levels = {}
188✔
200
   local base_level = typesetter.frame:writingDirection() == "RTL" and 1 or 0
376✔
201
   for i = 1, #nl do
3,596✔
202
      if nl[i].options and nl[i].options.bidilevel then
3,408✔
203
         levels[i] = { level = nl[i].options.bidilevel }
1,131✔
204
      end
205
   end
206
   for i = 1, #nl do
3,596✔
207
      if not levels[i] then
3,408✔
208
         -- resolve neutrals
209
         local left_level, right_level
210
         for left = i - 1, 1, -1 do
4,938✔
211
            if nl[left].options and nl[left].options.bidilevel then
4,123✔
212
               left_level = nl[left].options.bidilevel
1,462✔
213
               break
1,462✔
214
            end
215
         end
216
         for right = i + 1, #nl do
4,938✔
217
            if nl[right].options and nl[right].options.bidilevel then
4,094✔
218
               right_level = nl[right].options.bidilevel
1,433✔
219
               break
1,433✔
220
            end
221
         end
222
         levels[i] = { level = (left_level == right_level and left_level or 0) }
2,277✔
223
      end
224
   end
225
   local matrix = create_matrix(levels, 0)
188✔
226
   local rv = {}
188✔
227
   -- for i = 1, #nl do print(i, nl[i], levels[i]) end
228
   for i = 1, #nl do
3,596✔
229
      if nl[i].is_nnode and levels[i].level % 2 ~= base_level then
3,408✔
230
         SU.flip_in_place(nl[i].nodes)
64✔
231
         reverse_each_node(nl[i].nodes)
128✔
232
      elseif nl[i].is_discretionary and levels[i].level % 2 ~= base_level and not nl[i].bidiDone then
3,344✔
233
         for j = 1, #nl[i].replacement do
×
234
            if nl[i].replacement[j].is_nnode then
×
235
               SU.flip_in_place(nl[i].replacement[j].nodes)
×
236
               reverse_each_node(nl[i].replacement[j].nodes)
×
237
            end
238
         end
239
         for j = 1, #nl[i].prebreak do
×
240
            if nl[i].prebreak[j].is_nnode then
×
241
               SU.flip_in_place(nl[i].prebreak[j].nodes)
×
242
               reverse_each_node(nl[i].prebreak[j].nodes)
×
243
            end
244
         end
245
         for j = 1, #nl[i].postbreak do
×
246
            if nl[i].postbreak[j].is_nnode then
×
247
               SU.flip_in_place(nl[i].postbreak[j].nodes)
×
248
               reverse_each_node(nl[i].postbreak[j].nodes)
×
249
            end
250
         end
251
      end
252
      rv[matrix[i]] = nl[i]
3,408✔
253
      nl[i].bidiDone = true
3,408✔
254
      -- rv[i] = nl[i]
255
   end
256
   n.nodes = SU.compress(rv)
376✔
257
end
258

259
function package:bidiEnableTypesetter (typesetter)
22✔
260
   if typesetter.nobidi_boxUpNodes and self.class._initialized then
22✔
261
      return SU.warn("BiDi already enabled, nothing to turn on")
×
262
   end
263
   typesetter.nobidi_boxUpNodes = typesetter.boxUpNodes
22✔
264
   typesetter.boxUpNodes = bidiBoxupNodes
22✔
265
end
266

267
function package:bidiDisableTypesetter (typesetter)
22✔
268
   if not typesetter.nobidi_boxUpNodes and self.class._initialized then
2✔
269
      return SU.warn("BiDi not enabled, nothing to turn off")
×
270
   end
271
   typesetter.boxUpNodes = typesetter.nobidi_boxUpNodes
2✔
272
   typesetter.nobidi_boxUpNodes = nil
2✔
273
end
274

275
function package:_init ()
22✔
276
   base._init(self)
22✔
277
   self:deprecatedExport("reorder", self.reorder)
22✔
278
   self:deprecatedExport("bidiEnableTypesetter", self.bidiEnableTypesetter)
22✔
279
   self:deprecatedExport("bidiDisableTypesetter", self.bidiDisableTypesetter)
22✔
280
   if SILE.typesetter then
22✔
281
      self:bidiEnableTypesetter(SILE.typesetter)
×
282
   end
283
   self:bidiEnableTypesetter(SILE.typesetters.base)
44✔
284
end
285

286
function package:registerCommands ()
22✔
287
   self:registerCommand("thisframeLTR", function (_, _)
44✔
288
      local direction = "LTR"
×
289
      SILE.typesetter.frame.direction = direction
×
290
      SILE.settings:set("font.direction", direction)
×
291
      SILE.typesetter:leaveHmode()
×
292
      SILE.typesetter.frame:newLine()
×
293
   end)
294

295
   self:registerCommand("thisframedirection", function (options, _)
44✔
296
      local direction = SU.required(options, "direction", "frame direction")
×
297
      SILE.typesetter.frame.direction = direction
×
298
      SILE.settings:set("font.direction", direction)
×
299
      SILE.typesetter:leaveHmode()
×
300
      SILE.typesetter.frame:init()
×
301
   end)
302

303
   self:registerCommand("thisframeRTL", function (_, _)
44✔
304
      local direction = "RTL"
×
305
      SILE.typesetter.frame.direction = direction
×
306
      SILE.settings:set("font.direction", direction)
×
307
      SILE.typesetter:leaveHmode()
×
308
      SILE.typesetter.frame:newLine()
×
309
   end)
310

311
   self:registerCommand("bidi-on", function (_, _)
44✔
312
      self:bidiEnableTypesetter(SILE.typesetter)
×
313
   end)
314

315
   self:registerCommand("bidi-off", function (_, _)
44✔
316
      self:bidiDisableTypesetter(SILE.typesetter)
×
317
   end)
318
end
319

320
package.documentation = [[
321
\begin{document}
322
Scripts like the Latin alphabet you are currently reading are normally written left to right (LTR); however, some scripts, such as Arabic and Hebrew, are written right to left (RTL).
323
The \autodoc:package{bidi} package, which is loaded by default, provides SILE with the ability to correctly typeset right-to-left text and also documents which mix right-to-left and left-to-right typesetting.
324
Because it is loaded by default, you can use both LTR and RTL text within a paragraph and SILE will ensure that the output characters appear in the correct order.
325

326
The \autodoc:package{bidi} package provides two commands, \autodoc:command{\thisframeLTR} and \autodoc:command{\thisframeRTL}, which set the default text direction for the current frame.
327
If you tell SILE that a frame is RTL, the text will start in the right margin and proceed leftward.
328
It also provides the commands \autodoc:command{\bidi-off} and \autodoc:command{\bidi-on}, which allow you to trade off bidirectional support for a dubious increase in speed.
329
\end{document}
330
]]
22✔
331

332
return package
22✔
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