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

sile-typesetter / sile / 7232859119

16 Dec 2023 03:49PM UTC coverage: 66.878% (-7.7%) from 74.62%
7232859119

push

github

web-flow
Merge 05d75c2a3 into 8686730e4

0 of 4 new or added lines in 1 file covered. (0.0%)

1201 existing lines in 56 files now uncovered.

10550 of 15775 relevant lines covered (66.88%)

3347.52 hits per line

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

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

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

5
SILE.settings:declare({
71✔
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({
142✔
13

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

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

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

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

46
    makePenalty = function (self, p)
47
      if self.lastnode ~= "penalty" and self.lastnode ~= "glue" then
3,053✔
48
        coroutine.yield( SILE.nodefactory.penalty({ penalty = p or 0 }) )
78✔
49
      end
50
      self.lastnode = "penalty"
3,053✔
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)))
87✔
57
      self.lastnode = "glue"
29✔
58
      self.lasttype = "sp"
29✔
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)
99,726✔
67
      if not chardata[cp] then return {} end
99,726✔
68
      return chardata[cp]
98,874✔
69
    end,
70

71
    isPunctuation = function (self, char)
72
      return self.isPunctuationType[self:charData(char).category]
×
73
    end,
74

75
    isSpace = function (self, char)
76
      return self.isSpaceType[self:charData(char).linebreak]
64,144✔
77
    end,
78

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

84
    isActiveNonBreakingSpace = function (self, char)
85
      return self:isNonBreakingSpace(char) and not SILE.settings:get("languages.fixedNbsp")
49,741✔
86
    end,
87

88
    isBreaking = function (self, char)
89
      return self.isBreakingType[self:charData(char).linebreak]
42,776✔
90
    end,
91
    isQuote = function (self, char)
92
      return self.isQuoteType[self:charData(char).linebreak]
42,752✔
93
    end
94

95
  })
71✔
96

97
SILE.nodeMakers.unicode = pl.class(SILE.nodeMakers.base)
142✔
98

99
SILE.nodeMakers.unicode.isWordType = { cm = true }
71✔
100
SILE.nodeMakers.unicode.isSpaceType = { sp = true }
71✔
101
SILE.nodeMakers.unicode.isBreakingType = { ba = true, zw = true }
71✔
102
SILE.nodeMakers.unicode.isPunctuationType = { po = true }
71✔
103
SILE.nodeMakers.unicode.isQuoteType = {} -- quote linebreak category is ambiguous depending on the language
71✔
104

105
function SILE.nodeMakers.unicode:dealWith (item)
142✔
106
  local char = item.text
21,416✔
107
  local cp = SU.codepoint(char)
21,416✔
108
  local thistype = chardata[cp] and chardata[cp].linebreak
21,416✔
109
  if self:isSpace(item.text) then
42,832✔
110
    self:makeToken()
28✔
111
    self:makeGlue(item)
56✔
112
  elseif self:isActiveNonBreakingSpace(item.text) then
42,776✔
113
    self:makeToken()
×
114
    self:makeNonBreakingSpace()
×
115
  elseif self:isBreaking(item.text) then
42,776✔
116
    self:addToken(char, item)
12✔
117
    self:makeToken()
12✔
118
    self:makePenalty(0)
24✔
119
  elseif self:isQuote(item.text) then
42,752✔
UNCOV
120
    self:addToken(char, item)
×
UNCOV
121
    self:makeToken()
×
122
  elseif self.lasttype and (thistype and thistype ~= self.lasttype and not self.isWordType[thistype]) then
21,376✔
123
    self:addToken(char, item)
200✔
124
  else
125
    self:letterspace()
21,276✔
126
    self:addToken(char, item)
21,276✔
127
  end
128
  self.lasttype = thistype
21,416✔
129
end
130

131
function SILE.nodeMakers.unicode:handleInitialGlue (items)
142✔
132
  local i = 1
3,981✔
133
  while i <= #items do
4,051✔
134
    local item = items[i]
4,034✔
135
    if self:isSpace(item.text) then self:makeGlue(item) else break end
8,138✔
136
    i = i + 1
70✔
137
  end
138
  return i, items
3,981✔
139
end
140

141
function SILE.nodeMakers.unicode:letterspace ()
142✔
142
  if not SILE.settings:get("document.letterspaceglue") then return end
42,552✔
143
  if self.token then self:makeToken() end
55✔
144
  if self.lastnode and self.lastnode ~= "glue" then
55✔
145
    local w = SILE.settings:get("document.letterspaceglue").width
102✔
146
    SU.debug("tokenizer", "Letter space glue:", w)
51✔
147
    coroutine.yield(SILE.nodefactory.kern({ width = w }))
102✔
148
    self.lastnode = "glue"
51✔
149
    self.lasttype = "sp"
51✔
150
  end
151
end
152

153
function SILE.nodeMakers.unicode.isICUBreakHere (_, chunks, item)
142✔
154
  return chunks[1] and (item.index >= chunks[1].index)
27,998✔
155
end
156

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

177
function SILE.nodeMakers.unicode:handleWordBreak (item)
142✔
178
  self:makeToken()
3,536✔
179
  if self:isSpace(item.text) then
7,072✔
180
    -- Spacing word break
181
    self:makeGlue(item)
6,218✔
182
  elseif self:isActiveNonBreakingSpace(item.text) then
854✔
183
    -- Non-breaking space word break
184
    self:makeNonBreakingSpace()
58✔
185
  else
186
     -- a word break which isn't a space
187
    self:addToken(item.text, item)
398✔
188
  end
189
end
190

191
function SILE.nodeMakers.unicode:handleLineBreak (item, subtype)
142✔
192
  -- Because we are in charge of paragraphing, we
193
  -- will override space-type line breaks, and treat
194
  -- them just as ordinary word spaces.
195
  if self:isSpace(item.text) or self:isActiveNonBreakingSpace(item.text) then
9,123✔
196
    self:handleWordBreak(item)
×
197
    return
×
198
  end
199
  -- But explicit line breaks we will turn into
200
  -- soft and hard breaks.
201
  self:makeToken()
3,041✔
202
  self:makePenalty(subtype == "soft" and 0 or -1000)
3,041✔
203
  local char = item.text
3,041✔
204
  self:addToken(char, item)
3,041✔
205
  local cp = SU.codepoint(char)
3,041✔
206
  self.lasttype = chardata[cp] and chardata[cp].linebreak
3,041✔
207
end
208

209
function SILE.nodeMakers.unicode:iterator (items)
142✔
210
  local fulltext = ""
3,981✔
211
  for i = 1, #items do
32,049✔
212
    fulltext = fulltext .. items[i].text
28,068✔
213
  end
214
  local chunks = { icu.breakpoints(fulltext, self.options.language) }
3,981✔
215
  table.remove(chunks, 1)
3,981✔
216
  return coroutine.wrap(function ()
3,981✔
217
    local i
218
    i, self.items = self:handleInitialGlue(items)
7,962✔
219
    for j = i, #items do
31,979✔
220
      self.i = j
27,998✔
221
      self.item = self.items[self.i]
27,998✔
222
      if self:isICUBreakHere(chunks, self.item) then
55,996✔
223
        chunks = self:handleICUBreak(chunks, self.item)
13,162✔
224
      else
225
        self:dealWith(self.item)
21,417✔
226
      end
227
    end
228
    self:makeToken()
3,981✔
229
  end)
230
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