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

sile-typesetter / sile / 7299766145

22 Dec 2023 12:17PM UTC coverage: 73.971% (-0.8%) from 74.723%
7299766145

push

github

alerque
docs(readme): Freshed info and copy edit a bunch of sections

11748 of 15882 relevant lines covered (73.97%)

7076.46 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")
2✔
2

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

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

8
function fallbackQueue:_init (text, fallbacks)
2✔
9
  self.fallbacks = fallbacks
24✔
10
  self.runs = {
24✔
11
    {
12
      options = self:currentOptions(),
48✔
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()
48✔
18
    },
24✔
19
  }
24✔
20
  self._fallbacks = fallbacks
24✔
21
  self.text = text
24✔
22
  self.pending = nil
24✔
23
end
24

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

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

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

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

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

46
function fallbackQueue:currentText ()
2✔
47
  local run = self:currentRun()
24✔
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)
24✔
51
end
52

53
function fallbackQueue:addRun (offset, start)
2✔
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)
2✔
71
  if self.pending then
68✔
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 = {}
2✔
82

83
function shaper:shapeToken (text, options)
2✔
84
  local items = {}
24✔
85
  local fallbackOptions = { options }
24✔
86
  for _, font in ipairs(activeFallbacks) do
48✔
87
    table.insert(fallbackOptions, pl.tablex.merge(options, font, true))
48✔
88
  end
89
  local shapeQueue = fallbackQueue(text, fallbackOptions)
24✔
90
  repeat -- iterate fallbacks
91
    SU.debug("font-fallback", function ()
48✔
92
      return ("Start fallback iteration for text '%s'"):format(text)
×
93
    end)
94
    local run = shapeQueue:currentRun()
24✔
95
    local face = run.options.family:len() > 0 and run.options.family or run.options.filename
48✔
96
    local chunk = shapeQueue:currentText()
24✔
97
    SU.debug("font-fallback", function ()
48✔
98
      return ("Try shaping chunk '%s' with '%s'"):format(chunk, face)
×
99
    end)
100
    local candidate_items = self._base.shapeToken(self, chunk, run.options)
24✔
101
    local _index
102
    for _, item in ipairs(candidate_items) do
68✔
103
      item.fontOptions = run.options
44✔
104
      if item.gid == 0 or item.name == ".null" or item.name == ".notdef" then
44✔
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 ()
88✔
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
44✔
120
        if item.index == _index then
44✔
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
44✔
126
          run.offset = run.offset + 1
44✔
127
          table.insert(items, run.offset, item)
44✔
128
        end
129
      end
130
    end
131
    shapeQueue:pushNextRun(run.stop) -- if notdef run pending, end it
24✔
132
    shapeQueue:popRun()
24✔
133
  until not shapeQueue:currentRun()
48✔
134
  return items
24✔
135
end
136

137
function shaper:createNnodes (token, options)
2✔
138
  options.tracking = SILE.settings:get("shaper.tracking")
24✔
139
  local items, _ = self:shapeToken(token, options)
12✔
140
  if #items < 1 then return {} end
12✔
141
  local lang = options.language
12✔
142
  SILE.languageSupport.loadLanguage(lang)
12✔
143
  local nodeMaker = SILE.nodeMakers[lang] or SILE.nodeMakers.unicode
12✔
144
  local run = { [1] = { slice = {}, fontOptions = items[1].fontOptions, chunk = "" } }
12✔
145
  for i = 1, #items do
44✔
146
    if items[i].fontOptions ~= run[#run].fontOptions then
32✔
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
32✔
153
    run[#run].slice[#(run[#run].slice)+1] = items[i]
32✔
154
  end
155
  local nodes = {}
12✔
156
  for i=1, #run do
24✔
157
    options = run[i].fontOptions
12✔
158
    SU.debug("font-fallback", "Shaping", run[i].chunk, "in", options.family)
12✔
159
    for node in nodeMaker(options):iterator(run[i].slice, run[i].chunk) do
56✔
160
      nodes[#nodes+1] = node
20✔
161
    end
162
  end
163
  SU.debug("font-fallback", nodes)
12✔
164
  return nodes
12✔
165
end
166

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

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

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

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

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