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

sile-typesetter / sile / 8736205729

18 Apr 2024 10:16AM UTC coverage: 63.747% (-11.0%) from 74.718%
8736205729

push

github

alerque
chore(classes): Correct typos in user facing deprecation message

10118 of 15872 relevant lines covered (63.75%)

6751.63 hits per line

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

83.8
/languages/unicode.lua
1
local icu = require("justenoughicu")
50✔
2

3
local chardata = require("char-def")
50✔
4

5
SILE.settings:declare({
50✔
6
  parameter = "languages.fixedNbsp",
7
  type = "boolean",
8
  default = false,
9
  help = "Whether to treat U+00A0 (NO-BREAK SPACE) as a fixed-width space"
×
10
})
11

12
SILE.nodeMakers.base = pl.class({
100✔
13

14
    _init = function (self, options)
15
      self.contents = {}
1,905✔
16
      self.options = options
1,905✔
17
      self.token = ""
1,905✔
18
      self.lastnode = false
1,905✔
19
      self.lasttype = false
1,905✔
20
    end,
21

22
    makeToken = function (self)
23
      if #self.contents > 0 then
5,304✔
24
        coroutine.yield(SILE.shaper:formNnode(self.contents, self.token, self.options))
7,362✔
25
        SU.debug("tokenizer", "Token:", self.token)
3,681✔
26
        self.contents = {}
3,681✔
27
        self.token = ""
3,681✔
28
        self.lastnode = "nnode"
3,681✔
29
      end
30
    end,
31

32
    addToken = function (self, char, item)
33
      self.token = self.token .. char
12,174✔
34
      table.insert(self.contents, item)
12,174✔
35
    end,
36

37
    makeGlue = function (self, item)
38
      if SILE.settings:get("typesetter.obeyspaces") or self.lastnode ~= "glue" then
3,260✔
39
        SU.debug("tokenizer", "Space node")
1,609✔
40
        coroutine.yield(SILE.shaper:makeSpaceNode(self.options, item))
3,218✔
41
      end
42
      self.lastnode = "glue"
1,630✔
43
      self.lasttype = "sp"
1,630✔
44
    end,
45

46
    makePenalty = function (self, p)
47
      if self.lastnode ~= "penalty" and self.lastnode ~= "glue" then
1,509✔
48
        coroutine.yield( SILE.nodefactory.penalty({ penalty = p or 0 }) )
28✔
49
      end
50
      self.lastnode = "penalty"
1,509✔
51
    end,
52

53
    makeNonBreakingSpace = function (self)
54
      -- Unicode Line Breaking Algorithm (UAX 14) specifies that U+00A0
55
      -- (NO-BREAK SPACE) is expanded or compressed like a normal space.
56
      coroutine.yield(SILE.nodefactory.kern(SILE.shaper:measureSpace(self.options)))
21✔
57
      self.lastnode = "glue"
7✔
58
      self.lasttype = "sp"
7✔
59
    end,
60

61
    iterator = function (_, _)
62
      SU.error("Abstract function nodemaker:iterator called", true)
×
63
    end,
64

65
    charData = function (_, char)
66
      local cp = SU.codepoint(char)
48,739✔
67
      if not chardata[cp] then return {} end
48,739✔
68
      return chardata[cp]
47,887✔
69
    end,
70

71
    isActiveNonBreakingSpace = function (self, char)
72
      return self:isNonBreakingSpace(char) and not SILE.settings:get("languages.fixedNbsp")
24,369✔
73
    end,
74

75
    isBreaking = function (self, char)
76
      return self.breakingTypes[self:charData(char).linebreak]
20,790✔
77
    end,
78

79
    isNonBreakingSpace = function (self, char)
80
      local c = self:charData(char)
12,181✔
81
      return c.contextname and c.contextname == "nobreakspace"
12,181✔
82
    end,
83

84
    isPunctuation = function (self, char)
85
      return self.puctuationTypes[self:charData(char).category]
×
86
    end,
87

88
    isSpace = function (self, char)
89
      return self.spaceTypes[self:charData(char).linebreak]
31,390✔
90
    end,
91

92
    isQuote = function (self, char)
93
      return self.quoteTypes[self:charData(char).linebreak]
20,790✔
94
    end,
95

96
    isWord = function (self, char)
97
      return self.wordTypes[self:charData(char).linebreak]
146✔
98
    end,
99

100
  })
50✔
101

102
SILE.nodeMakers.unicode = pl.class(SILE.nodeMakers.base)
100✔
103

104
SILE.nodeMakers.unicode.breakingTypes = { ba = true, zw = true }
50✔
105
SILE.nodeMakers.unicode.puctuationTypes = { po = true }
50✔
106
SILE.nodeMakers.unicode.quoteTypes = {} -- quote linebreak category is ambiguous depending on the language
50✔
107
SILE.nodeMakers.unicode.spaceTypes = { sp = true }
50✔
108
SILE.nodeMakers.unicode.wordTypes = { cm = true }
50✔
109

110
function SILE.nodeMakers.unicode:dealWith (item)
100✔
111
  local char = item.text
10,398✔
112
  local cp = SU.codepoint(char)
10,398✔
113
  local thistype = chardata[cp] and chardata[cp].linebreak
10,398✔
114
  if self:isSpace(item.text) then
20,796✔
115
    self:makeToken()
3✔
116
    self:makeGlue(item)
6✔
117
  elseif self:isActiveNonBreakingSpace(item.text) then
20,790✔
118
    self:makeToken()
×
119
    self:makeNonBreakingSpace()
×
120
  elseif self:isBreaking(item.text) then
20,790✔
121
    self:addToken(char, item)
×
122
    self:makeToken()
×
123
    self:makePenalty(0)
×
124
  elseif self:isQuote(item.text) then
20,790✔
125
    self:addToken(char, item)
×
126
    self:makeToken()
×
127
  elseif self.lasttype and (thistype and thistype ~= self.lasttype and not self:isWord(thistype)) then
10,468✔
128
    self:addToken(char, item)
146✔
129
  else
130
    self:letterspace()
10,322✔
131
    self:addToken(char, item)
10,322✔
132
  end
133
  self.lasttype = thistype
10,398✔
134
end
135

136
function SILE.nodeMakers.unicode:handleInitialGlue (items)
100✔
137
  local i = 1
1,903✔
138
  while i <= #items do
1,973✔
139
    local item = items[i]
1,954✔
140
    if self:isSpace(item.text) then self:makeGlue(item) else break end
3,978✔
141
    i = i + 1
70✔
142
  end
143
  return i, items
1,903✔
144
end
145

146
function SILE.nodeMakers.unicode:letterspace ()
100✔
147
  if not SILE.settings:get("document.letterspaceglue") then return end
20,644✔
148
  if self.token then self:makeToken() end
55✔
149
  if self.lastnode and self.lastnode ~= "glue" then
55✔
150
    local w = SILE.settings:get("document.letterspaceglue").width
102✔
151
    SU.debug("tokenizer", "Letter space glue:", w)
51✔
152
    coroutine.yield(SILE.nodefactory.kern({ width = w }))
102✔
153
    self.lastnode = "glue"
51✔
154
    self.lasttype = "sp"
51✔
155
  end
156
end
157

158
function SILE.nodeMakers.unicode.isICUBreakHere (_, chunks, item)
100✔
159
  return chunks[1] and (item.index >= chunks[1].index)
13,741✔
160
end
161

162
function SILE.nodeMakers.unicode:handleICUBreak (chunks, item)
100✔
163
  -- The ICU library has told us there is a breakpoint at
164
  -- this index. We need to...
165
  local bp = chunks[1]
3,343✔
166
  -- ... remove this breakpoint (and any out of order ones)
167
  -- from the ICU breakpoints array so that chunks[1] is
168
  -- the next index point for comparison against the string...
169
  while chunks[1] and item.index >= chunks[1].index do
6,686✔
170
    table.remove(chunks, 1)
6,686✔
171
  end
172
  -- ...decide which kind of breakpoint we have here and
173
  -- handle it appropriately.
174
  if bp.type == "word" then
3,343✔
175
    self:handleWordBreak(item)
3,668✔
176
  elseif bp.type == "line" then
1,509✔
177
    self:handleLineBreak(item, bp.subtype)
1,509✔
178
  end
179
  return chunks
3,343✔
180
end
181

182
function SILE.nodeMakers.unicode:handleWordBreak (item)
100✔
183
  self:makeToken()
1,834✔
184
  if self:isSpace(item.text) then
3,668✔
185
    -- Spacing word break
186
    self:makeGlue(item)
3,114✔
187
  elseif self:isActiveNonBreakingSpace(item.text) then
554✔
188
    -- Non-breaking space word break
189
    self:makeNonBreakingSpace()
14✔
190
  else
191
     -- a word break which isn't a space
192
    self:addToken(item.text, item)
270✔
193
  end
194
end
195

196
function SILE.nodeMakers.unicode:_handleWordBreakRepeatHyphen (item)
100✔
197
  -- According to some language rules, when a break occurs at an explicit hyphen,
198
  -- the hyphen gets repeated at the beginning of the new line
199
  if item.text == "-" then
×
200
    self:addToken(item.text, item)
×
201
    self:makeToken()
×
202
    if self.lastnode ~= "discretionary" then
×
203
      coroutine.yield(SILE.nodefactory.discretionary({
×
204
        postbreak = SILE.shaper:createNnodes("-", self.options)
×
205
      }))
206
      self.lastnode = "discretionary"
×
207
    end
208
  else
209
    SILE.nodeMakers.unicode.handleWordBreak(self, item)
×
210
  end
211
end
212

213
function SILE.nodeMakers.unicode:handleLineBreak (item, subtype)
100✔
214
  -- Because we are in charge of paragraphing, we
215
  -- will override space-type line breaks, and treat
216
  -- them just as ordinary word spaces.
217
  if self:isSpace(item.text) or self:isActiveNonBreakingSpace(item.text) then
4,527✔
218
    self:handleWordBreak(item)
×
219
    return
×
220
  end
221
  -- But explicit line breaks we will turn into
222
  -- soft and hard breaks.
223
  self:makeToken()
1,509✔
224
  self:makePenalty(subtype == "soft" and 0 or -1000)
1,509✔
225
  local char = item.text
1,509✔
226
  self:addToken(char, item)
1,509✔
227
  local cp = SU.codepoint(char)
1,509✔
228
  self.lasttype = chardata[cp] and chardata[cp].linebreak
1,509✔
229
end
230

231
function SILE.nodeMakers.unicode:_handleLineBreakRepeatHyphen (item, subtype)
100✔
232
  if self.lastnode == "discretionary" then
×
233
    -- Initial word boundary after a discretionary:
234
    -- Bypass it and just deal with the token.
235
    self:dealWith(item)
×
236
  else
237
    SILE.nodeMakers.unicode.handleLineBreak(self, item, subtype)
×
238
  end
239
end
240

241
function SILE.nodeMakers.unicode:iterator (items)
100✔
242
  local fulltext = ""
1,903✔
243
  for i = 1, #items do
15,714✔
244
    fulltext = fulltext .. items[i].text
13,811✔
245
  end
246
  local chunks = { icu.breakpoints(fulltext, self.options.language) }
1,903✔
247
  table.remove(chunks, 1)
1,903✔
248
  return coroutine.wrap(function ()
1,903✔
249
    local i
250
    i, self.items = self:handleInitialGlue(items)
3,806✔
251
    for j = i, #items do
15,644✔
252
      self.i = j
13,741✔
253
      self.item = self.items[self.i]
13,741✔
254
      if self:isICUBreakHere(chunks, self.item) then
27,482✔
255
        chunks = self:handleICUBreak(chunks, self.item)
6,686✔
256
      else
257
        self:dealWith(self.item)
10,398✔
258
      end
259
    end
260
    self:makeToken()
1,903✔
261
  end)
262
end
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