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

sile-typesetter / sile / 5913811407

19 Aug 2023 10:21PM UTC coverage: 73.575% (-0.8%) from 74.334%
5913811407

push

github

web-flow
Merge pull request #1849 from alerque/docs-note-style

10 of 10 new or added lines in 2 files covered. (100.0%)

11591 of 15754 relevant lines covered (73.57%)

6930.04 hits per line

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

71.79
/shapers/fallback.lua
1
local harfbuzz = require("shapers.harfbuzz")
4✔
2

3
local shaper = pl.class(harfbuzz)
4✔
4
shaper._name = "fallback"
4✔
5

6
local fallbackQueue = pl.class()
4✔
7

8
function fallbackQueue:_init (text, fallbacks)
4✔
9
  self.fallbacks = fallbacks
123✔
10
  self.runs = {
123✔
11
    {
12
      options = self:currentOptions(),
246✔
13
      offset = 0,
14
      start = 1,
15
      -- WARNING: shaper index is in bytes, not UTF8 aware character
16
      -- lengths so do *not* use luautf8.len() here
17
      stop = text:len()
246✔
18
    },
123✔
19
  }
123✔
20
  self._fallbacks = fallbacks
123✔
21
  self.text = text
123✔
22
  self.pending = nil
123✔
23
end
24

25
function fallbackQueue:popFallback ()
4✔
26
  return table.remove(self.fallbacks, 1)
123✔
27
end
28

29
function fallbackQueue:popRun ()
4✔
30
  self:popFallback()
123✔
31
  return table.remove(self.runs, 1)
123✔
32
end
33

34
function fallbackQueue:currentOptions ()
4✔
35
  return self.fallbacks[1]
123✔
36
end
37

38
function fallbackQueue:nextFallback ()
4✔
39
  return self.fallbacks[2]
×
40
end
41

42
function fallbackQueue:currentRun ()
4✔
43
  return self.runs[1]
369✔
44
end
45

46
function fallbackQueue:currentText ()
4✔
47
  local run = self:currentRun()
123✔
48
  -- WARNING: shaper index is in bytes, not UTF8 aware character
49
  -- lengths so do *not* use luautf8.sub() here
50
  return self.text:sub(run.start, run.stop)
123✔
51
end
52

53
function fallbackQueue:addRun (offset, start)
4✔
54
  if not self.pending then
×
55
    SU.debug("font-fallback", function ()
×
56
      return ("New run pending for %s starting byte %s insert at %s"):format(self:currentText(), start, offset)
×
57
    end)
58
    local options = self:nextFallback()
×
59
    if not options then return false end
×
60
    options.size = SILE.measurement(options.size):tonumber()
×
61
    self.pending = {
×
62
      options = options,
63
      offset = offset,
64
      start = start
×
65
    }
66
  end
67
  return true
×
68
end
69

70
function fallbackQueue:pushNextRun (stop)
4✔
71
  if self.pending then
289✔
72
    SU.debug("font-fallback", function ()
×
73
      return ("Push pending run for %s ending at %s"):format(self:currentText(), stop)
×
74
    end)
75
    self.pending.stop = stop
×
76
    table.insert(self.runs, self.pending)
×
77
    self.pending = nil
×
78
  end
79
end
80

81
local activeFallbacks = {}
4✔
82

83
function shaper:shapeToken (text, options)
4✔
84
  local items = {}
123✔
85
  local fallbackOptions = { options }
123✔
86
  for _, font in ipairs(activeFallbacks) do
246✔
87
    table.insert(fallbackOptions, pl.tablex.merge(options, font, true))
246✔
88
  end
89
  local shapeQueue = fallbackQueue(text, fallbackOptions)
123✔
90
  repeat -- iterate fallbacks
91
    SU.debug("font-fallback", function ()
246✔
92
      return ("Start fallback iteration for text '%s'"):format(text)
×
93
    end)
94
    local run = shapeQueue:currentRun()
123✔
95
    local face = run.options.family:len() > 0 and run.options.family or run.options.filename
246✔
96
    local chunk = shapeQueue:currentText()
123✔
97
    SU.debug("font-fallback", function ()
246✔
98
      return ("Try shaping chunk '%s' with '%s'"):format(chunk, face)
×
99
    end)
100
    local candidate_items = self._base.shapeToken(self, chunk, run.options)
123✔
101
    local _index
102
    for _, item in ipairs(candidate_items) do
289✔
103
      item.fontOptions = run.options
166✔
104
      if item.gid == 0 or item.name == ".null" or item.name == ".notdef" then
166✔
105
        SU.debug("font-fallback", function ()
×
106
          return ("Glyph %s not found in %s"):format(item.text, face)
×
107
        end)
108
        local newstart = run.start + item.index
×
109
        local pending = shapeQueue:addRun(run.offset, newstart)
×
110
        if not pending then
×
111
          SU.warn(("Glyph(s) '%s' not available in any fallback font,\n  run with '-d font-fallback' for more detail.\n"):format(item.text))
×
112
          run.offset = run.offset + 1
×
113
          table.insert(items, run.offset, item) -- output tofu if we're out of fallbacks
×
114
        end
115
      else
116
        SU.debug("font-fallback", function ()
332✔
117
          return ("Found glyph '%s' in '%s'"):format(item.text, face)
×
118
        end)
119
        shapeQueue:pushNextRun(run.start + item.index - 1) -- if notdef run pending, end it
166✔
120
        if item.index == _index then
166✔
121
          local previous = items[run.offset]
×
122
          while previous.next do previous = previous.next end
×
123
          previous.next = item
×
124
        else
125
          _index = run.index
166✔
126
          run.offset = run.offset + 1
166✔
127
          table.insert(items, run.offset, item)
166✔
128
        end
129
      end
130
    end
131
    shapeQueue:pushNextRun(run.stop) -- if notdef run pending, end it
123✔
132
    shapeQueue:popRun()
123✔
133
  until not shapeQueue:currentRun()
246✔
134
  return items
123✔
135
end
136

137
function shaper:createNnodes (token, options)
4✔
138
  options.tracking = SILE.settings:get("shaper.tracking")
58✔
139
  local items, _ = self:shapeToken(token, options)
29✔
140
  if #items < 1 then return {} end
29✔
141
  local lang = options.language
29✔
142
  SILE.languageSupport.loadLanguage(lang)
29✔
143
  local nodeMaker = SILE.nodeMakers[lang] or SILE.nodeMakers.unicode
29✔
144
  local run = { [1] = { slice = {}, fontOptions = items[1].fontOptions, chunk = "" } }
29✔
145
  for i = 1, #items do
101✔
146
    if items[i].fontOptions ~= run[#run].fontOptions then
72✔
147
      run[#run+1] = { slice = {}, chunk = "", fontOptions = items[i].fontOptions }
×
148
      if i <#items then
×
149
        run[#run].fontOptions = items[i].fontOptions
×
150
      end
151
    end
152
    run[#run].chunk = run[#run].chunk .. items[i].text
72✔
153
    run[#run].slice[#(run[#run].slice)+1] = items[i]
72✔
154
  end
155
  local nodes = {}
29✔
156
  for i=1, #run do
58✔
157
    options = run[i].fontOptions
29✔
158
    SU.debug("font-fallback", "Shaping", run[i].chunk, "in", options.family)
29✔
159
    for node in nodeMaker(options):iterator(run[i].slice, run[i].chunk) do
186✔
160
      nodes[#nodes+1] = node
99✔
161
    end
162
  end
163
  SU.debug("font-fallback", nodes)
29✔
164
  return nodes
29✔
165
end
166

167
function shaper.clearFallbacks(_)
4✔
168
  activeFallbacks = {}
×
169
end
170

171
function shaper.addFallback(_, options)
4✔
172
  table.insert(activeFallbacks, options)
4✔
173
end
174

175
function shaper.removeFallback(_)
4✔
176
  table.remove(activeFallbacks)
1✔
177
end
178

179
function shaper.dumpFallbacks(_)
4✔
180
  return activeFallbacks
1✔
181
end
182

183
return shaper
4✔
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