• 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

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

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

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

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

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

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

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

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

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

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

53
function fallbackQueue:addRun (offset, start)
1✔
UNCOV
54
  if not self.pending then
×
UNCOV
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)
UNCOV
58
    local options = self:nextFallback()
×
UNCOV
59
    if not options then return false end
×
UNCOV
60
    options.size = SILE.measurement(options.size):tonumber()
×
UNCOV
61
    self.pending = {
×
62
      options = options,
63
      offset = offset,
UNCOV
64
      start = start
×
65
    }
66
  end
UNCOV
67
  return true
×
68
end
69

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

81
local activeFallbacks = {}
1✔
82

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

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

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

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

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

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

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