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

sile-typesetter / sile / 9409557472

07 Jun 2024 12:09AM UTC coverage: 69.448% (-4.5%) from 73.988%
9409557472

push

github

alerque
fix(build): Distribute vendored compat-5.3.c source file

12025 of 17315 relevant lines covered (69.45%)

6023.46 hits per line

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

48.05
/packages/frametricks/init.lua
1
local base = require("packages.base")
5✔
2

3
local package = pl.class(base)
5✔
4
package._name = "frametricks"
5✔
5

6
local breakFrameHorizontalAt = function (offset)
7
   local cFrame = SILE.typesetter.frame
×
8
   if not offset or not (offset > SILE.types.length(0)) then
×
9
      SILE.typesetter:chuck()
×
10
      offset = SILE.typesetter.frame.state.cursorX
×
11
   end
12
   local newFrame = SILE.newFrame({
×
13
      bottom = cFrame:bottom(),
14
      top = cFrame:top(),
15
      left = cFrame:left() + offset,
16
      right = cFrame:right(),
17
      next = cFrame.next,
18
      previous = cFrame,
19
      id = cFrame.id .. "_",
20
   })
21
   -- if SILE.scratch.insertions and SILE.scratch.insertions.classes['footnote'] and SILE.scratch.insertions.classes['footnote'].stealFrom then
22
   -- SILE.scratch.insertions.classes['footnote'].stealFrom[newFrame.id] = 1
23
   -- end
24
   local oldLeft = cFrame:left()
×
25
   cFrame.next = newFrame.id
×
26
   cFrame:constrain("left", oldLeft)
×
27
   cFrame:constrain("right", oldLeft + offset)
×
28
   -- SILE.outputter:debugFrame(cFrame)
29
   -- SILE.outputter:debugFrame(newFrame)
30
   SILE.typesetter:initFrame(newFrame)
×
31
end
32

33
local shiftframeedge = function (frame, options)
34
   if options.left then
×
35
      frame:constrain("left", frame:left() + SILE.types.length(options.left))
×
36
   end
37
   if options.right then
×
38
      frame:constrain("right", frame:right() + SILE.types.length(options.right))
×
39
   end
40
end
41

42
local makecolumns = function (options)
43
   local cFrame = SILE.typesetter.frame
1✔
44
   local cols = options.columns
1✔
45
   local balanced = SU.boolean(options.balanced, true)
1✔
46
   local gutterWidth = options.gutter or "3%pw"
1✔
47
   local right = cFrame:right()
1✔
48
   local origId = cFrame.id
1✔
49
   for i = 1, cols - 1 do
4✔
50
      local gutter = SILE.newFrame({
6✔
51
         width = gutterWidth,
3✔
52
         left = "right(" .. cFrame.id .. ")",
3✔
53
         id = origId .. "_gutter" .. i,
3✔
54
      })
55
      cFrame:relax("right")
3✔
56
      cFrame:constrain("right", "left(" .. gutter.id .. ")")
3✔
57
      local newFrame = SILE.newFrame({
6✔
58
         top = cFrame:top(),
6✔
59
         bottom = cFrame:bottom(),
6✔
60
         id = origId .. "_col" .. i,
3✔
61
      })
62
      newFrame.balanced = balanced
3✔
63
      cFrame.balanced = balanced
3✔
64
      gutter:constrain("right", "left(" .. newFrame.id .. ")")
3✔
65
      newFrame:constrain("left", "right(" .. gutter.id .. ")")
3✔
66
      -- In the future we may way to allow for unequal columns
67
      -- but for now just assume they will be equal.
68
      newFrame:constrain("width", "width(" .. cFrame.id .. ")")
3✔
69
      cFrame.next = newFrame.id
3✔
70
      cFrame = newFrame
3✔
71
   end
72
   cFrame:constrain("right", right)
1✔
73
end
74

75
local mergeColumns = function ()
76
   -- 1) Balance all remaining material.
77

78
   -- 1.1) Run the pagebuilder once to clear out any full pages
79
   SILE.typesetter:buildPage()
×
80

81
   -- 1.2) Find out the shape of the columnset. (It will change after we balance it)
82
   local frame = SILE.typesetter.frame
×
83
   -- local left = frame:left()
84
   -- local bottom = frame:bottom()
85
   while frame.next and SILE.getFrame(frame.next).balanced do
×
86
      frame = SILE.getFrame(frame.next)
×
87
   end
88
   -- local right = frame:right()
89

90
   -- 1.3) Now force a balance, which will resize the frames
91
   SILE.call("balancecolumns")
×
92
   SILE.typesetter:buildPage()
×
93

94
   -- 2) Add a new frame, the width of the old frameset and the height of
95
   -- old frameset - new height, at the end of the current frame
96
   local newId = SILE.typesetter.frame.id .. "_"
×
97
   SILE.typesetter.frame.next = newId
×
98
   SILE.typesetter:initNextFrame()
×
99
end
100

101
function package.breakFrameVertical (_, after)
5✔
102
   local cFrame = SILE.typesetter.frame
×
103
   local totalHeight
104
   if after then
×
105
      totalHeight = after
×
106
   else
107
      totalHeight = SILE.types.length(0)
×
108
      SILE.typesetter:leaveHmode(1)
×
109
      local queue = SILE.typesetter.state.outputQueue
×
110
      for i = 1, #queue do
×
111
         totalHeight = totalHeight + queue[i].height + queue[i].depth
×
112
      end
113
      SILE.typesetter:chuck()
×
114
   end
115

116
   local newFrame = SILE.newFrame({
×
117
      bottom = cFrame:bottom(),
118
      left = cFrame:left(),
119
      right = cFrame:right(),
120
      next = cFrame.next,
121
      previous = cFrame,
122
      id = cFrame.id .. "_",
123
   })
124
   if
125
      SILE.scratch.insertions
×
126
      and SILE.scratch.insertions.classes["footnote"]
×
127
      and SILE.scratch.insertions.classes["footnote"].stealFrom
×
128
   then
129
      SILE.scratch.insertions.classes["footnote"].stealFrom[newFrame.id] = 1
×
130
   end
131

132
   cFrame:relax("bottom")
×
133
   cFrame:constrain("height", totalHeight)
×
134
   cFrame.next = newFrame.id
×
135
   SILE.documentState.thisPageTemplate.frames[newFrame.id] = newFrame
×
136
   newFrame:constrain("top", cFrame:top() + totalHeight)
×
137
   if after then
×
138
      SILE.typesetter:initFrame(cFrame)
×
139
   else
140
      SILE.typesetter:initFrame(newFrame)
×
141
   end
142
   -- SILE.outputter:debugFrame(cFrame)
143
   -- SILE.outputter:debugFrame(newFrame)
144
end
145

146
function package:_init ()
5✔
147
   base._init(self)
5✔
148
   self:loadPackage("balanced-frames")
5✔
149
   self:export("breakFrameVertical", self.breakFrameVertical)
5✔
150
end
151

152
function package:registerCommands ()
5✔
153
   self:registerCommand("mergecolumns", function (_, _)
10✔
154
      mergeColumns()
×
155
   end, "Merge multiple columns into one")
5✔
156

157
   self:registerCommand("showframe", function (options, _)
10✔
158
      local id = options.id or SILE.typesetter.frame.id
6✔
159
      if id == "all" then
6✔
160
         for _, frame in pairs(SILE.frames) do
28✔
161
            SILE.outputter:debugFrame(frame)
24✔
162
         end
163
      else
164
         SILE.outputter:debugFrame(SILE.getFrame(id))
4✔
165
      end
166
   end)
167

168
   self:registerCommand("shiftframeedge", function (options, _)
10✔
169
      local cFrame = SILE.typesetter.frame
×
170
      shiftframeedge(cFrame, options)
×
171
      SILE.typesetter:initFrame(cFrame)
×
172
      --SILE.outputter:debugFrame(cFrame)
173
   end, "Adjusts the edge of the frame horizontally by amounts specified in <left> and <right>")
5✔
174

175
   self:registerCommand(
10✔
176
      "breakframevertical",
5✔
177
      function (options, _)
178
         self:breakFrameVertical(options.offset)
×
179
      end,
180
      "Breaks the current frame in two vertically at the current location or at a point <offset> below the current location"
181
   )
5✔
182

183
   self:registerCommand("makecolumns", function (options, _)
10✔
184
      -- Set a default value for column count
185
      options.columns = options.columns or 2
1✔
186
      local current_frame = SILE.typesetter.frame
1✔
187
      local original_constraints = {}
1✔
188
      -- Collect existing constraints that may need updating after makecolumns() changes them
189
      for frameid in pairs(SILE.frames) do
5✔
190
         if frameid ~= current_frame.id then
4✔
191
            local frame = SILE.getFrame(frameid)
3✔
192
            for method in pairs(frame.constraints) do
15✔
193
               -- TODO: Remove the assumption about direction when makecolumns() takes into account frame advance direction
194
               if method == "right" then
12✔
195
                  if frame[method](frame) == current_frame[method](current_frame) then
9✔
196
                     table.insert(original_constraints, { frame = frame, method = method })
2✔
197
                  end
198
               end
199
            end
200
         end
201
      end
202
      makecolumns(options)
1✔
203
      for _, info in ipairs(original_constraints) do
3✔
204
         local frame, method = info.frame, info.method
2✔
205
         local final_column_id = ("%s_col%d"):format(current_frame.id, options.columns - 1)
2✔
206
         local final_comumn_frame = SILE.getFrame(final_column_id)
2✔
207
         frame:constrain(method, final_comumn_frame[method](final_comumn_frame))
4✔
208
      end
209
   end, "Split the current frame into multiple columns")
6✔
210

211
   self:registerCommand(
10✔
212
      "breakframehorizontal",
5✔
213
      function (options, _)
214
         breakFrameHorizontalAt(options.offset)
×
215
      end,
216
      "Breaks the current frame in two horizontally either at the current location or at a point <offset> from the left of the current frame"
217
   )
5✔
218

219
   self:registerCommand("float", function (options, content)
10✔
220
      SILE.typesetter:leaveHmode()
×
221
      local hbox = SILE.typesetter:makeHbox(content) -- HACK What about migrating nodes here?
×
222
      local heightOfPageSoFar = SILE.pagebuilder:collateVboxes(SILE.typesetter.state.outputQueue).height
×
223
      local overshoot = SILE.types.length(heightOfPageSoFar + hbox.height - SILE.typesetter:getTargetLength())
×
224
      if overshoot > SILE.types.length(0) then
×
225
         SILE.call("eject")
×
226
         SILE.typesetter:leaveHmode()
×
227
      end
228
      self:breakFrameVertical()
×
229
      local boundary = hbox.width + SILE.types.length(options.rightboundary):absolute()
×
230
      breakFrameHorizontalAt(boundary)
×
231
      SILE.typesetNaturally(SILE.typesetter.frame.previous, function ()
×
232
         table.insert(SILE.typesetter.state.nodes, hbox)
×
233
      end)
234
      -- SILE.settings:set("document.baselineskip", SILE.types.length("1ex") - SILE.settings:get("document.baselineskip").height)
235
      -- undoSkip.stretch = hbox.height
236
      -- SILE.typesetter:pushHbox({ value = {} })
237
      -- SILE.typesetter:pushVglue({ height = undoSkip })
238
      self:breakFrameVertical(hbox.height + SILE.types.length(options.bottomboundary):absolute())
×
239
      shiftframeedge(SILE.getFrame(SILE.typesetter.frame.next), { left = -boundary })
×
240
      --SILE.outputter:debugFrame(SILE.typesetter.frame)
241
   end, "Sets the given content in its own frame, flowing the remaining content around it")
5✔
242

243
   self:registerCommand("typeset-into", function (options, content)
10✔
244
      SU.required(options, "frame", "calling \\typeset-into")
×
245
      if not SILE.frames[options.frame] then
×
246
         SU.error("Can't find frame " .. options.frame .. " to typeset into")
×
247
      end
248
      SILE.typesetNaturally(SILE.frames[options.frame], function ()
×
249
         SILE.process(content)
×
250
      end)
251
   end)
252

253
   self:registerCommand("fit-frame", function (options, _)
10✔
254
      SU.required(options, "frame", "calling \\fit-frame")
×
255
      if not SILE.frames[options.frame] then
×
256
         SU.error("Can't find frame " .. options.frame .. " to fit")
×
257
      end
258
      local frame = SILE.frames[options.frame]
×
259
      local height = SILE.types.length()
×
260
      SILE.typesetNaturally(frame, function ()
×
261
         SILE.typesetter:leaveHmode()
×
262
         for i = 1, #SILE.typesetter.state.outputQueue do
×
263
            height = height + SILE.typesetter.state.outputQueue[i].height
×
264
         end
265
      end)
266
      frame:constrain("height", frame:height() + height)
×
267
   end)
268
end
269

270
package.documentation = [[
271
\begin{document}
272
The \autodoc:package{frametricks} package assists package authors by providing a number of commands to manipulate frames.
273

274
The most immediately useful is \autodoc:command{\showframe}.
275
This asks the output engine to draw a box and label around a particular frame.
276
It takes an optional parameter \autodoc:parameter{id=<frame id>}; if this is not supplied, the current frame is used.
277
If the ID is \code{all}, then all frames declared by the current class are displayed.
278

279
It’s possible to define frames such as sidebars which are not connected to the main text flow of a page.
280
We’ll see how to do that in a later chapter, but this raises the obvious question: if they’re not part of the text flow, how do we get stuff into them?
281
\autodoc:package{frametricks} provides the \autodoc:command{\typeset-into} command, which allows you to write text into a specified frame:
282

283
\begin[type=autodoc:codeblock]{raw}
284
\typeset-into[frame=sidebar]{ ... frame content here ... }
285
\end{raw}
286

287
\autodoc:package{frametricks} also provides a number of commands which, to be perfectly honest, we \em{thought} were going to be useful, but haven’t quite ended up being as useful as all that.
288

289
\breakframevertical\par
290
The command \autodoc:command{\breakframevertical} breaks the current frame in two at the specified location into an upper and lower frame.
291
If the frame initially had the ID \code{main}, then \code{main} becomes the upper frame (before the command) and the lower frame (after the command) is called \code{main_}.
292
We just issued a \autodoc:command{\breakframevertical} command at the start of this paragraph, and now we will issue the command \autodoc:command{\showframe}.
293
\showframe
294
As you can see, the current frame is called \code{\script{SILE.typesetter:typeset(SILE.typesetter.frame.id)}} and now begins at the start of the paragraph.
295

296
Similarly, the \autodoc:command{\breakframehorizontal} command breaks the frame in two into a left and a right frame.
297
The command takes an optional parameter \autodoc:parameter{offset=<dimension>}, specifying where on the line the frame should be split.
298
If \autodoc:parameter{offset} is not supplied, the frame is split at the current position in the line.
299

300
The command \autodoc:command{\shiftframeedge} allows you to reposition the current frame left or right.
301
It takes \autodoc:parameter{left} and/or \autodoc:parameter{right} parameters, which can be positive or negative dimensions.
302
It should only be used at the top of a frame, as it reinitializes the typesetter object.
303

304
Combining all of these commands, the \autodoc:command{\float} command breaks the current frame, creates a small frame to hold a floating object, flows text into the surrounding frame, and then, once text has descended past the floating object, moves the frame back into place again.
305
It takes two optional parameters, \autodoc:parameter{bottomboundary=<dimension>} and/or \autodoc:parameter{rightboundary=<dimension>}, which open up additional space around the frame.
306

307
% At the start of this paragraph, I issued the command
308
%
309
% \begin[type=autodoc:codeblock]{raw}
310
% \float[bottomboundary=5pt]{\font[size=60pt]{C}}
311
% \end{raw}
312

313
To reiterate: we started playing around with frames like this in the early days of SILE and they have not really proved a good solution to the things we wanted to do with them.
314
They’re great for arranging where content should live on the page, but messing about with them dynamically seems to create more problems than it solves.
315
There’s probably a reason why InDesign and similar applications handle floats, drop caps, tables, and so on inside the context of a content frame rather than by messing with the frames themselves.
316
If you feel tempted to play with \autodoc:package{frametricks}, there’s almost always a better way to achieve what you want without it.
317
\end{document}
318
]]
5✔
319

320
return package
5✔
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