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

sile-typesetter / sile / 14860011647

06 May 2025 12:44PM UTC coverage: 67.057% (+32.5%) from 34.559%
14860011647

push

github

alerque
chore(typesetters): Fixup access to class from typesetter functions

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

1344 existing lines in 103 files now uncovered.

14880 of 22190 relevant lines covered (67.06%)

11549.16 hits per line

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

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

4
local module = require("types.module")
153✔
5
local shaper = pl.class(module)
153✔
6
shaper.type = "shaper"
153✔
7

8
-- local smallTokenSize = 20 -- Small words will be cached
9
-- local shapeCache = {}
10
-- local _key = function (options)
11
--   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 }, ";")
12
-- end
13

14
function shaper:_init ()
153✔
15
   -- Function for testing shaping in the repl
16
   -- TODO, figure out a way to explicitly register things in the repl env
17
   _G["makenodes"] = function (token, options)
153✔
UNCOV
18
      return SILE.shaper:createNnodes(token, SILE.font.loadDefaults(options or {}))
×
19
   end
20
   module._init(self)
153✔
21
end
22

23
function shaper:_declareSettings ()
153✔
24
   self.settings:declare({
306✔
25
      parameter = "shaper.variablespaces",
26
      type = "boolean",
27
      default = true,
28
   })
29
   self.settings:declare({
306✔
30
      parameter = "shaper.spaceenlargementfactor",
31
      type = "number or integer",
32
      default = 1,
33
   })
34
   self.settings:declare({
306✔
35
      parameter = "shaper.spacestretchfactor",
36
      type = "number or integer",
37
      default = 1 / 2,
38
   })
39
   self.settings:declare({
306✔
40
      parameter = "shaper.spaceshrinkfactor",
41
      type = "number or integer",
42
      default = 1 / 3,
43
   })
44
   self.settings:declare({
306✔
45
      parameter = "shaper.tracking",
46
      type = "number or nil",
47
      default = nil,
48
   })
49
end
50

51
function shaper:_shapespace (spacewidth)
153✔
52
   spacewidth = SU.cast("measurement", spacewidth)
11,304✔
53
   -- In some scripts with word-level kerning, glue can be negative.
54
   -- Use absolute value to ensure stretch and shrink work as expected.
55
   local abs_length = math.abs(spacewidth:tonumber())
11,304✔
56
   local length, stretch, shrink = abs_length, 0, 0
5,652✔
57
   if self.settings:get("shaper.variablespaces") then
16,956✔
58
      length = spacewidth * self.settings:get("shaper.spaceenlargementfactor")
16,950✔
59
      stretch = abs_length * self.settings:get("shaper.spacestretchfactor")
16,950✔
60
      shrink = abs_length * self.settings:get("shaper.spaceshrinkfactor")
16,950✔
61
   end
62
   return SILE.types.length(length, stretch, shrink)
5,652✔
63
end
64

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

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

109
-- Given a text and some font options, return a bunch of boxes
110
function shaper:shapeToken (_, _)
153✔
UNCOV
111
   SU.error("Abstract function shapeToken called", true)
×
112
end
113

114
-- Given font options, select a font. We will handle
115
-- caching here. Returns an arbitrary, implementation-specific
116
-- object (ie a PAL for Pango, font number for libtexpdf, ...)
117
function shaper:getFace ()
153✔
UNCOV
118
   SU.error("Abstract function getFace called", true)
×
119
end
120

121
-- TODO: Refactor so this isn't needed when the font module is refactored
122
function shaper:_getFaceCallback ()
153✔
123
   return function (options)
124
      return self:getFace(options)
321✔
125
   end
126
end
127

128
function shaper:addShapedGlyphToNnodeValue (_, _)
153✔
UNCOV
129
   SU.error("Abstract function addShapedGlyphToNnodeValue called", true)
×
130
end
131

132
function shaper:preAddNodes (_, _) end
153✔
133

134
function shaper:createNnodes (token, options)
153✔
135
   options.tracking = self.settings:get("shaper.tracking")
33,840✔
136
   local items, _ = self:shapeToken(token, options)
11,280✔
137
   if #items < 1 then
11,280✔
UNCOV
138
      return {}
×
139
   end
140
   -- TODO this shouldn't need a private interface to a different module type
141
   local language = SILE.typesetter:_cacheLanguage(options.language)
11,280✔
142
   local nodes = {}
11,280✔
143
   local nodemaker = language:nodemaker(options)
11,280✔
144
   for node in nodemaker:iterator(items, token) do
45,980✔
145
      table.insert(nodes, node)
23,420✔
146
   end
147
   return nodes
11,280✔
148
end
149

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

206
function shaper:makeSpaceNode (options, item)
153✔
207
   local width
208
   if self.settings:get("shaper.variablespaces") then
16,896✔
209
      width = self:_shapespace(item.width)
11,238✔
210
   else
211
      width = SILE.shaper:measureSpace(options)
26✔
212
   end
213
   return SILE.types.node.glue(width)
5,632✔
214
end
215

216
function shaper:debugVersions () end
153✔
217

218
return shaper
153✔
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