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

sile-typesetter / sile / 14361761590

09 Apr 2025 03:53PM UTC coverage: 29.317% (-31.1%) from 60.462%
14361761590

push

github

alerque
chore(deps): Bump LuaRocks dependencies to latest versions

5876 of 20043 relevant lines covered (29.32%)

4237.53 hits per line

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

77.59
/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)
648✔
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())
648✔
15
   local length, stretch, shrink = abs_length, 0, 0
324✔
16
   if SILE.settings:get("shaper.variablespaces") then
648✔
17
      length = spacewidth * SILE.settings:get("shaper.spaceenlargementfactor")
648✔
18
      stretch = abs_length * SILE.settings:get("shaper.spacestretchfactor")
648✔
19
      shrink = abs_length * SILE.settings:get("shaper.spaceshrinkfactor")
648✔
20
   end
21
   return SILE.types.length(length, stretch, shrink)
324✔
22
end
23

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

28
function shaper:_init ()
3✔
29
   SU._avoid_base_class_use(self)
3✔
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)
3✔
33
      return SILE.shaper:createNnodes(token, SILE.font.loadDefaults(options or {}))
×
34
   end
35
   self:_declareBaseSettings()
3✔
36
   self:declareSettings()
3✔
37
end
38

39
function shaper:declareSettings () end
3✔
40

41
function shaper:_declareBaseSettings ()
3✔
42
   SILE.settings:declare({
3✔
43
      parameter = "shaper.variablespaces",
44
      type = "boolean",
45
      default = true,
46
   })
47
   SILE.settings:declare({
3✔
48
      parameter = "shaper.spaceenlargementfactor",
49
      type = "number or integer",
50
      default = 1,
51
   })
52
   SILE.settings:declare({
3✔
53
      parameter = "shaper.spacestretchfactor",
54
      type = "number or integer",
55
      default = 1 / 2,
56
   })
57
   SILE.settings:declare({
3✔
58
      parameter = "shaper.spaceshrinkfactor",
59
      type = "number or integer",
60
      default = 1 / 3,
61
   })
62
   SILE.settings:declare({
3✔
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)
3✔
74
   local ss = SILE.settings:get("document.spaceskip")
×
75
   if ss then
×
76
      SILE.settings:temporarily(function ()
×
77
         SILE.settings:set("font.size", options.size)
×
78
         SILE.settings:set("font.family", options.family)
×
79
         SILE.settings:set("font.filename", options.filename)
×
80
         ss = ss:absolute()
×
81
      end)
82
      return ss
×
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)
3✔
93
   local options = SILE.font.loadDefaults({})
8✔
94
   options.tracking = SILE.settings:get("shaper.tracking")
16✔
95
   local items = self:shapeToken(char, options)
8✔
96
   if items and #items > 0 then
8✔
97
      local measurements = {
8✔
98
         width = 0,
99
         height = 0,
100
         depth = 0,
101
      }
102
      for _, item in ipairs(items) do
16✔
103
         measurements.width = measurements.width + item.width
8✔
104
         measurements.height = math.max(measurements.height, item.height)
8✔
105
         measurements.depth = math.max(measurements.depth, item.depth)
8✔
106
      end
107
      return measurements, items[1].gid ~= 0
8✔
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 (_, _)
3✔
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 ()
3✔
122
   SU.error("Abstract function getFace called", true)
×
123
end
124

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

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

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

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

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

213
function shaper:debugVersions () end
3✔
214

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