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

sile-typesetter / sile / 6713098919

31 Oct 2023 10:21PM UTC coverage: 52.831% (-21.8%) from 74.636%
6713098919

push

github

web-flow
Merge d0a2a1ee9 into b185d4972

45 of 45 new or added lines in 3 files covered. (100.0%)

8173 of 15470 relevant lines covered (52.83%)

6562.28 hits per line

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

0.0
/packages/frametricks/init.lua
1
local base = require("packages.base")
×
2

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

6
local breakFrameHorizontalAt = function (offset)
7
  local cFrame = SILE.typesetter.frame
×
8
  if not offset or not (offset > SILE.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.length(options.left))
×
36
  end
37
  if options.right then
×
38
    frame:constrain("right", frame:right() + SILE.length(options.right))
×
39
  end
40
end
41

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

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

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

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

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

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

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

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

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

141

142
function package:_init ()
×
143
  base._init(self)
×
144
  self:loadPackage("balanced-frames")
×
145
  self:export("breakFrameVertical", self.breakFrameVertical)
×
146
end
147

148
function package:registerCommands ()
×
149

150
  self:registerCommand("mergecolumns", function (_, _)
×
151
    mergeColumns()
×
152
  end, "Merge multiple columns into one")
×
153

154
  self:registerCommand("showframe", function (options, _)
×
155
    local id = options.id or SILE.typesetter.frame.id
×
156
    if id == "all" then
×
157
      for _, frame in pairs(SILE.frames) do
×
158
        SILE.outputter:debugFrame(frame)
×
159
      end
160
    else
161
      SILE.outputter:debugFrame(SILE.getFrame(id))
×
162
    end
163
  end)
164

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

172
  self:registerCommand("breakframevertical", function (options, _)
×
173
    self:breakFrameVertical(options.offset)
×
174
  end, "Breaks the current frame in two vertically at the current location or at a point <offset> below the current location")
×
175

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

204
  self:registerCommand("breakframehorizontal", function (options, _)
×
205
    breakFrameHorizontalAt(options.offset)
×
206
  end, "Breaks the current frame in two horizontally either at the current location or at a point <offset> from the left of the current frame")
×
207

208
  self:registerCommand("float", function (options, content)
×
209
    SILE.typesetter:leaveHmode()
×
210
    local hbox = SILE.typesetter:makeHbox(content) -- HACK What about migrating nodes here?
×
211
    local heightOfPageSoFar = SILE.pagebuilder:collateVboxes(SILE.typesetter.state.outputQueue).height
×
212
    local overshoot = SILE.length(heightOfPageSoFar + hbox.height - SILE.typesetter:getTargetLength())
×
213
    if overshoot > SILE.length(0) then
×
214
      SILE.call("eject")
×
215
      SILE.typesetter:leaveHmode()
×
216
    end
217
    self:breakFrameVertical()
×
218
    local boundary = hbox.width + SILE.length(options.rightboundary):absolute()
×
219
    breakFrameHorizontalAt(boundary)
×
220
    SILE.typesetNaturally(SILE.typesetter.frame.previous, function ()
×
221
      table.insert(SILE.typesetter.state.nodes, hbox)
×
222
    end)
223
    -- SILE.settings:set("document.baselineskip", SILE.length("1ex") - SILE.settings:get("document.baselineskip").height)
224
    -- undoSkip.stretch = hbox.height
225
    -- SILE.typesetter:pushHbox({ value = {} })
226
    -- SILE.typesetter:pushVglue({ height = undoSkip })
227
    self:breakFrameVertical(hbox.height + SILE.length(options.bottomboundary):absolute())
×
228
    shiftframeedge(SILE.getFrame(SILE.typesetter.frame.next), { left = -boundary })
×
229
    --SILE.outputter:debugFrame(SILE.typesetter.frame)
230
  end, "Sets the given content in its own frame, flowing the remaining content around it")
×
231

232
  self:registerCommand("typeset-into", function (options, content)
×
233
    SU.required(options, "frame", "calling \\typeset-into")
×
234
    if not SILE.frames[options.frame] then
×
235
      SU.error("Can't find frame "..options.frame.." to typeset into")
×
236
    end
237
    SILE.typesetNaturally(SILE.frames[options.frame], function () SILE.process(content) end)
×
238
  end)
239

240
  self:registerCommand("fit-frame", function (options, _)
×
241
    SU.required(options, "frame", "calling \\fit-frame")
×
242
    if not SILE.frames[options.frame] then
×
243
      SU.error("Can't find frame "..options.frame.." to fit")
×
244
    end
245
    local frame = SILE.frames[options.frame]
×
246
    local height = SILE.length()
×
247
    SILE.typesetNaturally(frame, function ()
×
248
      SILE.typesetter:leaveHmode()
×
249
      for i = 1, #SILE.typesetter.state.outputQueue do
×
250
        height = height + SILE.typesetter.state.outputQueue[i].height
×
251
      end
252
    end)
253
    frame:constrain("height", frame:height() + height)
×
254
  end)
255

256
end
257

258
package.documentation = [[
259
\begin{document}
260
The \autodoc:package{frametricks} package assists package authors by providing a number of commands to manipulate frames.
261

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

267
It’s possible to define frames such as sidebars which are not connected to the main text flow of a page.
268
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?
269
\autodoc:package{frametricks} provides the \autodoc:command{\typeset-into} command, which allows you to write text into a specified frame:
270

271
\begin[type=autodoc:codeblock]{raw}
272
\typeset-into[frame=sidebar]{ ... frame content here ... }
273
\end{raw}
274

275
\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.
276

277
\breakframevertical\par
278
The command \autodoc:command{\breakframevertical} breaks the current frame in two at the specified location into an upper and lower frame.
279
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_}.
280
We just issued a \autodoc:command{\breakframevertical} command at the start of this paragraph, and now we will issue the command \autodoc:command{\showframe}.
281
\showframe
282
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.
283

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

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

292
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.
293
It takes two optional parameters, \autodoc:parameter{bottomboundary=<dimension>} and/or \autodoc:parameter{rightboundary=<dimension>}, which open up additional space around the frame.
294

295
% At the start of this paragraph, I issued the command
296
%
297
% \begin[type=autodoc:codeblock]{raw}
298
% \float[bottomboundary=5pt]{\font[size=60pt]{C}}
299
% \end{raw}
300

301
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.
302
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.
303
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.
304
If you feel tempted to play with \autodoc:package{frametricks}, there’s almost always a better way to achieve what you want without it.
305
\end{document}
306
]]
×
307

308
return package
×
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