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

sile-typesetter / sile / 6915845768

18 Nov 2023 07:23PM UTC coverage: 63.161% (-5.6%) from 68.751%
6915845768

push

github

web-flow
Merge 0f5c09a66 into f64e235fa

9802 of 15519 relevant lines covered (63.16%)

2045.54 hits per line

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

74.85
/core/frame.lua
1
SILE.frames = {}
96✔
2

3
-- Buggy, relies on side effects
4
-- luacheck: ignore solver frame
5
-- See https://github.com/sile-typesetter/sile/issues/694
6

7
local cassowary = require("cassowary")
96✔
8
local solver = cassowary.SimplexSolver()
96✔
9
local solverNeedsReloading = true
96✔
10

11
local widthdims = pl.Set { "left", "right", "width" }
96✔
12
local heightdims = pl.Set { "top", "bottom", "height" }
96✔
13
local alldims = widthdims + heightdims
96✔
14

15
SILE.framePrototype = pl.class({
192✔
16
    direction = "LTR-TTB",
17
    enterHooks = {},
96✔
18
    leaveHooks = {},
96✔
19

20
    -- This gets called by Penlght when creating the frame instance
21
    _init = function (self, spec, dummy)
22
      local direction = SILE.documentState.direction
258✔
23
      if direction then
258✔
24
        self.direction = direction
6✔
25
      end
26
      self.constraints = {}
258✔
27
      self.variables = {}
258✔
28
      self.id = spec.id
258✔
29
      for k, v in pairs(spec) do
1,555✔
30
        if not alldims[k] then self[k] = v end
1,297✔
31
      end
32
      self.balanced = SU.boolean(self.balanced, false)
516✔
33
      if not dummy then
258✔
34
        for method in pairs(alldims) do
1,806✔
35
          self.variables[method] = cassowary.Variable({ name = spec.id .. "_" .. method })
3,096✔
36
          self[method] = function (instance_self)
37
            instance_self:solve()
2,077✔
38
            return SILE.measurement(instance_self.variables[method].value)
2,077✔
39
          end
40
        end
41
        -- Add definitions of width and height
42
        for method in pairs(alldims) do
1,806✔
43
          if spec[method] then
1,548✔
44
            self:constrain(method, spec[method])
1,003✔
45
          end
46
        end
47
      end
48
    end,
49

50
    -- This gets called by us in typesetter before we start to use the frame
51
    init = function (self, typesetter)
52
      self.state = { totals = { height = SILE.measurement(0) } }
292✔
53
      self:enter(typesetter)
146✔
54
      self:newLine(typesetter)
146✔
55
      if self:pageAdvanceDirection() == "TTB" then
292✔
56
        self.state.cursorY = self:top()
290✔
57
      elseif self:pageAdvanceDirection() == "LTR" then
2✔
58
        self.state.cursorX = self:left()
×
59
      elseif self:pageAdvanceDirection() == "RTL" then
2✔
60
        self.state.cursorX = self:right()
2✔
61
      elseif self:pageAdvanceDirection() == "BTT" then
×
62
        self.state.cursorY = self:bottom()
×
63
      end
64
    end,
65

66
    constrain = function (self, method, dimension)
67
      self.constraints[method] = tostring(dimension)
1,049✔
68
      self:invalidate()
1,027✔
69
    end,
70

71
    invalidate = function ()
72
      solverNeedsReloading = true
1,163✔
73
    end,
74

75
    relax = function (self, method)
76
      self.constraints[method] = nil
12✔
77
    end,
78

79
    reifyConstraint = function (self, solver, method, stay)
80
      local constraint = self.constraints[method]
1,953✔
81
      if not constraint then return end
1,953✔
82
      constraint = SU.type(constraint) == "measurement"
3,906✔
83
        and constraint:tonumber()
×
84
        or SILE.frameParser:match(constraint)
1,953✔
85
      SU.debug("frames", "Adding constraint", self.id, function ()
3,906✔
86
        return "(" .. method .. ") = " .. tostring(constraint)
×
87
      end)
88
      local eq = cassowary.Equation(self.variables[method], constraint)
1,953✔
89
      solver:addConstraint(eq)
1,953✔
90
      if stay then solver:addStay(eq) end
1,953✔
91
    end,
92

93
    addWidthHeightDefinitions = function (self, solver)
94
      local vars = self.variables
494✔
95
      solver:addConstraint(cassowary.Equation(vars.width, cassowary.minus(vars.right, vars.left)))
1,482✔
96
      solver:addConstraint(cassowary.Equation(vars.height, cassowary.minus(vars.bottom, vars.top)))
1,482✔
97
    end,
98

99
    -- This is hideously inefficient,
100
    -- but it's the easiest way to allow users to reconfigure frames at runtime.
101
    solve = function (_)
102
      if not solverNeedsReloading then return end
2,077✔
103
      SU.debug("frames", "Solving...")
101✔
104
      solver = cassowary.SimplexSolver()
202✔
105
      if SILE.frames.page then
101✔
106
        for method, _ in pairs(SILE.frames.page.constraints) do
505✔
107
          SILE.frames.page:reifyConstraint(solver, method, true)
404✔
108
        end
109
        SILE.frames.page:addWidthHeightDefinitions(solver)
101✔
110
      end
111
      for id, frame in pairs(SILE.frames) do
595✔
112
        if not (id == "page") then
494✔
113
          for method, _ in pairs(frame.constraints) do
1,942✔
114
            frame:reifyConstraint(solver, method)
1,549✔
115
          end
116
          frame:addWidthHeightDefinitions(solver)
393✔
117
        end
118
      end
119
      solver:solve()
101✔
120
      solverNeedsReloading = false
101✔
121
    end,
122

123
    writingDirection = function (self)
124
      return self.direction:match("^(%a+)") or "LTR"
22,158✔
125
    end,
126

127
    pageAdvanceDirection = function (self)
128
      return self.direction:match("-(%a+)$") or "TTB"
2,242✔
129
    end,
130

131
    advanceWritingDirection = function (self, length)
132
      local amount = SU.cast("number", length)
5,163✔
133
      if amount == 0 then return end
5,163✔
134
      if self:writingDirection() == "RTL" then
8,694✔
135
        self.state.cursorX = self.state.cursorX - amount
434✔
136
      elseif self:writingDirection() == "LTR" then
8,260✔
137
        self.state.cursorX = self.state.cursorX + amount
8,248✔
138
      elseif self:writingDirection() == "TTB" then
12✔
139
        self.state.cursorY = self.state.cursorY + amount
12✔
140
      elseif self:writingDirection() == "BTT" then
×
141
        self.state.cursorY = self.state.cursorY - amount
×
142
      end
143
    end,
144

145
    advancePageDirection = function (self, length)
146
      local amount = SU.cast("number", length)
1,557✔
147
      if amount == 0 then return end
1,557✔
148
      if self:pageAdvanceDirection() == "TTB" then
2,860✔
149
        self.state.cursorY = self.state.cursorY + amount
2,854✔
150
      elseif self:pageAdvanceDirection() == "RTL" then
6✔
151
        self.state.cursorX = self.state.cursorX - amount
6✔
152
      elseif self:pageAdvanceDirection() == "LTR" then
×
153
        self.state.cursorX = self.state.cursorX + amount
×
154
      elseif self:pageAdvanceDirection() == "BTT" then
×
155
        self.state.cursorY = self.state.cursorY - amount
×
156
      end
157
    end,
158

159
    newLine = function(self, _)
160
      if self:writingDirection() == "LTR" then
1,066✔
161
        self.state.cursorX = self:left()
1,022✔
162
      elseif self:writingDirection() == "RTL" then
44✔
163
        self.state.cursorX = self:right()
40✔
164
      elseif self:writingDirection() == "TTB" then
4✔
165
        self.state.cursorY = self:top()
4✔
166
      elseif self:writingDirection() == "BTT" then
×
167
        self.state.cursorY = self:bottom()
×
168
      end
169
    end,
170

171
    lineWidth = function (self)
172
      SU.warn("Method :lineWidth() is deprecated, please use :getLineWidth()")
×
173
      return self:getLineWidth()
×
174
    end,
175

176
    getLineWidth = function (self)
177
      if self:writingDirection() == "LTR" or self:writingDirection() == "RTL" then
688✔
178
        return self:width()
338✔
179
      else
180
        return self:height()
1✔
181
      end
182
    end,
183

184
    pageTarget = function (self)
185
      SU.warn("Method :pageTarget() is deprecated, please use :getTargetLength()")
×
186
      return self:getTargetLength()
×
187
    end,
188

189
    getTargetLength = function (self)
190
      local direction = self:pageAdvanceDirection()
661✔
191
      if direction == "TTB" or direction == "BTT" then
661✔
192
        return self:height()
657✔
193
      else
194
        return self:width()
4✔
195
      end
196
    end,
197

198
    enter = function (self, typesetter)
199
      for i = 1, #self.enterHooks do
191✔
200
        self.enterHooks[i](self, typesetter)
3✔
201
      end
202
    end,
203

204
    leave = function (self, typesetter)
205
      for i = 1, #self.leaveHooks do
198✔
206
        self.leaveHooks[i](self, typesetter)
9✔
207
      end
208
    end,
209

210
    isAbsoluteConstraint = function (self, method)
211
      if not self.constraints[method] then return false end
64✔
212
      local constraint = SILE.frameParser:match(self.constraints[method])
63✔
213
      if type(constraint) ~= "table" then return true end
63✔
214
      if not constraint.terms then return false end
48✔
215
      for clv, _ in pairs(constraint.terms) do
2✔
216
        if clv.name and not clv.name:match("^page_") then
2✔
217
          return false
2✔
218
        end
219
      end
220
      return true
×
221
    end,
222

223
    isMainContentFrame = function (self)
224
      local tpt = SILE.documentState.thisPageTemplate
33✔
225
      local frame = tpt.firstContentFrame
33✔
226
      while frame do
35✔
227
        if frame == self then return true end
35✔
228
        if frame.next then frame = SILE.getFrame(frame.next) else return false end
18✔
229
      end
230
      return false
×
231
    end,
232

233
    __tostring = function(self)
234
      local str = "<Frame: " .. self.id .. ": "
×
235
      str = str .. " next=" .. (self.next or "nil") .. " "
×
236
      for method, dimension in pairs(self.constraints) do
×
237
        str = str .. method .. "=" .. dimension .. "; "
×
238
      end
239
      if self.hanmen then
×
240
        str = str .. "tate=" .. (self.tate and "true" or "false") .. "; "
×
241
        str = str .. "gridsize=" .. self.gridsize .. "; "
×
242
        str = str .. "linegap=" .. self.linegap .. "; "
×
243
        str = str .. "linelength=" .. self.linelength .. "; "
×
244
        str = str .. "linecount=" .. self.linecount .. "; "
×
245
      end
246
      str = str .. ">"
×
247
      return str
×
248
    end
249
  })
96✔
250

251
SILE.newFrame = function (spec, prototype)
96✔
252
  SU.required(spec, "id", "frame declaration")
258✔
253
  prototype = prototype or SILE.framePrototype
258✔
254
  local frame = prototype(spec)
258✔
255
  SILE.frames[spec.id] = frame
258✔
256
  return frame
258✔
257
end
258

259
SILE.getFrame = function (id)
96✔
260
  if type(id) == "table" then
93✔
261
    SU.error("Passed a table, expected a string", true)
×
262
  end
263
  local frame, last_attempt
264
  while not frame do
186✔
265
    frame = SILE.frames[id]
94✔
266
    id = id:gsub("_$", "")
94✔
267
    if id == last_attempt then break end
94✔
268
    last_attempt = id
93✔
269
  end
270
  return frame or SU.warn("Couldn't find frame ID "..id, true)
94✔
271
end
272

273
SILE.parseComplexFrameDimension = function (dimension)
96✔
274
  local length = SILE.frameParser:match(SU.cast("string", dimension))
×
275
  if type(length) == "table" then
×
276
    local g = cassowary.Variable({ name = "t" })
×
277
    local eq = cassowary.Equation(g, length)
×
278
    solverNeedsReloading = true
×
279
    solver:addConstraint(eq)
×
280
    SILE.frames.page:solve()
×
281
    solverNeedsReloading = true
×
282
    return g.value
×
283
  end
284
  return length
×
285
end
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