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

sile-typesetter / sile / 14958605596

11 May 2025 06:34PM UTC coverage: 31.311% (-25.4%) from 56.689%
14958605596

push

github

web-flow
Merge 3e53926d5 into 443551a3e

6301 of 20124 relevant lines covered (31.31%)

4203.99 hits per line

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

88.79
/shapers/base.lua
1
--- SILE shaper class.
2
-- @interfaces shapers
3

4
-- local smallTokenSize = 20 -- Small words will be cached
5
-- local shapeCache = {}
6
-- local _key = function (options)
7
--   return table.concat({ options.family;options.language;options.script;options.size;("%d"):format(options.weight);options.style;options.variant;options.features;options.variations;options.direction;options.filename }, ";")
8
-- end
9

10
local function shapespace (spacewidth)
11
   spacewidth = SU.cast("measurement", spacewidth)
666✔
12
   -- In some scripts with word-level kerning, glue can be negative.
13
   -- Use absolute value to ensure stretch and shrink work as expected.
14
   local abs_length = math.abs(spacewidth:tonumber())
666✔
15
   local length, stretch, shrink = abs_length, 0, 0
333✔
16
   if SILE.settings:get("shaper.variablespaces") then
666✔
17
      length = spacewidth * SILE.settings:get("shaper.spaceenlargementfactor")
666✔
18
      stretch = abs_length * SILE.settings:get("shaper.spacestretchfactor")
666✔
19
      shrink = abs_length * SILE.settings:get("shaper.spaceshrinkfactor")
666✔
20
   end
21
   return SILE.types.length(length, stretch, shrink)
333✔
22
end
23

24
local shaper = pl.class()
7✔
25
shaper.type = "shaper"
7✔
26
shaper._name = "base"
7✔
27

28
function shaper:_init ()
7✔
29
   SU._avoid_base_class_use(self)
7✔
30
   -- Function for testing shaping in the repl
31
   -- TODO, figure out a way to explicitly register things in the repl env
32
   _G["makenodes"] = function (token, options)
7✔
33
      return SILE.shaper:createNnodes(token, SILE.font.loadDefaults(options or {}))
×
34
   end
35
   self:_declareBaseSettings()
7✔
36
   self:declareSettings()
7✔
37
end
38

39
function shaper:declareSettings () end
7✔
40

41
function shaper:_declareBaseSettings ()
7✔
42
   SILE.settings:declare({
7✔
43
      parameter = "shaper.variablespaces",
44
      type = "boolean",
45
      default = true,
46
   })
47
   SILE.settings:declare({
7✔
48
      parameter = "shaper.spaceenlargementfactor",
49
      type = "number or integer",
50
      default = 1,
51
   })
52
   SILE.settings:declare({
7✔
53
      parameter = "shaper.spacestretchfactor",
54
      type = "number or integer",
55
      default = 1 / 2,
56
   })
57
   SILE.settings:declare({
7✔
58
      parameter = "shaper.spaceshrinkfactor",
59
      type = "number or integer",
60
      default = 1 / 3,
61
   })
62
   SILE.settings:declare({
7✔
63
      parameter = "shaper.tracking",
64
      type = "number or nil",
65
      default = nil,
66
   })
67
end
68

69
-- Return the length of a space character
70
-- with a particular set of font options,
71
-- giving preference to document.spaceskip
72
-- Caching this has no significant speedup
73
function shaper:measureSpace (options)
7✔
74
   local ss = SILE.settings:get("document.spaceskip")
8✔
75
   if ss then
8✔
76
      SILE.settings:temporarily(function ()
16✔
77
         SILE.settings:set("font.size", options.size)
8✔
78
         SILE.settings:set("font.family", options.family)
8✔
79
         SILE.settings:set("font.filename", options.filename)
8✔
80
         ss = ss:absolute()
16✔
81
      end)
82
      return ss
8✔
83
   end
84
   local items, width = self:shapeToken(" ", options)
×
85
   if not width and not items[1] then
×
86
      SU.warn("Could not measure the width of a space")
×
87
      return SILE.types.length()
×
88
   end
89
   return shapespace(width and width.length or items[1].width)
×
90
end
91

92
function shaper:measureChar (char)
7✔
93
   local options = SILE.font.loadDefaults({})
26✔
94
   options.tracking = SILE.settings:get("shaper.tracking")
52✔
95
   local items = self:shapeToken(char, options)
26✔
96
   if items and #items > 0 then
26✔
97
      local measurements = {
26✔
98
         width = 0,
99
         height = 0,
100
         depth = 0,
101
      }
102
      for _, item in ipairs(items) do
52✔
103
         measurements.width = measurements.width + item.width
26✔
104
         measurements.height = math.max(measurements.height, item.height)
26✔
105
         measurements.depth = math.max(measurements.depth, item.depth)
26✔
106
      end
107
      return measurements, items[1].gid ~= 0
26✔
108
   else
109
      SU.error("Unable to measure character", char)
×
110
   end
111
end
112

113
-- Given a text and some font options, return a bunch of boxes
114
function shaper:shapeToken (_, _)
7✔
115
   SU.error("Abstract function shapeToken called", true)
×
116
end
117

118
-- Given font options, select a font. We will handle
119
-- caching here. Returns an arbitrary, implementation-specific
120
-- object (ie a PAL for Pango, font number for libtexpdf, ...)
121
function shaper:getFace ()
7✔
122
   SU.error("Abstract function getFace called", true)
×
123
end
124

125
function shaper:addShapedGlyphToNnodeValue (_, _)
7✔
126
   SU.error("Abstract function addShapedGlyphToNnodeValue called", true)
×
127
end
128

129
function shaper:preAddNodes (_, _) end
7✔
130

131
function shaper:createNnodes (token, options)
7✔
132
   options.tracking = SILE.settings:get("shaper.tracking")
1,902✔
133
   local items, _ = self:shapeToken(token, options)
951✔
134
   if #items < 1 then
951✔
135
      return {}
×
136
   end
137
   local lang = options.language
951✔
138
   SILE.languageSupport.loadLanguage(lang)
951✔
139
   local nodeMaker = SILE.nodeMakers[lang] or SILE.nodeMakers.unicode
951✔
140
   local nodes = {}
951✔
141
   for node in nodeMaker(options):iterator(items, token) do
4,552✔
142
      table.insert(nodes, node)
1,699✔
143
   end
144
   return nodes
951✔
145
end
146

147
function shaper:formNnode (contents, token, options)
7✔
148
   local nnodeContents = {}
1,331✔
149
   -- local glyphs = {}
150
   local totalWidth = 0
1,331✔
151
   local totalDepth = 0
1,331✔
152
   local totalHeight = 0
1,331✔
153
   -- local glyphNames = {}
154
   local nnodeValue = { text = token, options = options, glyphString = {} }
1,331✔
155
   SILE.shaper:preAddNodes(contents, nnodeValue)
1,331✔
156
   local misfit = false
1,331✔
157
   if SILE.typesetter.frame and SILE.typesetter.frame:writingDirection() == "TTB" then
2,662✔
158
      if options.direction == "LTR" then
2✔
159
         misfit = true
×
160
      end
161
   else
162
      if options.direction == "TTB" then
1,329✔
163
         misfit = true
×
164
      end
165
   end
166
   for i = 1, #contents do
4,918✔
167
      local glyph = contents[i]
3,587✔
168
      if (options.direction == "TTB") ~= misfit then
3,587✔
169
         if glyph.width > totalHeight then
2✔
170
            totalHeight = glyph.width
2✔
171
         end
172
         totalWidth = totalWidth + glyph.height
2✔
173
      else
174
         if glyph.depth > totalDepth then
3,585✔
175
            totalDepth = glyph.depth
931✔
176
         end
177
         if glyph.height > totalHeight then
3,585✔
178
            totalHeight = glyph.height
1,668✔
179
         end
180
         totalWidth = totalWidth + glyph.width
3,585✔
181
      end
182
      self:addShapedGlyphToNnodeValue(nnodeValue, glyph)
3,587✔
183
   end
184
   table.insert(
2,662✔
185
      nnodeContents,
1,331✔
186
      SILE.types.node.hbox({
2,662✔
187
         depth = totalDepth,
1,331✔
188
         height = totalHeight,
1,331✔
189
         misfit = misfit,
1,331✔
190
         width = SILE.types.length(totalWidth),
2,662✔
191
         value = nnodeValue,
1,331✔
192
      })
193
   )
194
   return SILE.types.node.nnode({
1,331✔
195
      nodes = nnodeContents,
1,331✔
196
      text = token,
1,331✔
197
      misfit = misfit,
1,331✔
198
      options = options,
1,331✔
199
      language = options.language,
1,331✔
200
   })
1,331✔
201
end
202

203
function shaper:makeSpaceNode (options, item)
7✔
204
   local width
205
   if SILE.settings:get("shaper.variablespaces") then
682✔
206
      width = shapespace(item.width)
666✔
207
   else
208
      width = SILE.shaper:measureSpace(options)
16✔
209
   end
210
   return SILE.types.node.glue(width)
341✔
211
end
212

213
function shaper:debugVersions () end
7✔
214

215
return shaper
7✔
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