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

sile-typesetter / sile / 6713098919

31 Oct 2023 10:21PM UTC coverage: 52.831% (-21.8%) from 74.636%
6713098919

push

github

web-flow
Merge d0a2a1ee9 into b185d4972

45 of 45 new or added lines in 3 files covered. (100.0%)

8173 of 15470 relevant lines covered (52.83%)

6562.28 hits per line

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

0.0
/shapers/fallback.lua
1
local harfbuzz = require("shapers.harfbuzz")
×
2

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

6
local fallbackQueue = pl.class()
×
7

8
function fallbackQueue:_init (text, fallbacks)
×
9
  self.fallbacks = fallbacks
×
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
17
      stop = text:len()
×
18
    },
19
  }
20
  self._fallbacks = fallbacks
×
21
  self.text = text
×
22
  self.pending = nil
×
23
end
24

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

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

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

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

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

46
function fallbackQueue:currentText ()
×
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
50
  return self.text:sub(run.start, run.stop)
×
51
end
52

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

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

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

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

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

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

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

183
return shaper
×
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

© 2025 Coveralls, Inc