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

sile-typesetter / sile / 9304049654

30 May 2024 02:12PM UTC coverage: 60.021% (-14.7%) from 74.707%
9304049654

push

github

web-flow
Merge 1a26b4f22 into a1fd105f8

6743 of 12900 new or added lines in 186 files covered. (52.27%)

347 existing lines in 49 files now uncovered.

10311 of 17179 relevant lines covered (60.02%)

3307.34 hits per line

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

95.77
/packages/twoside/init.lua
1
local base = require("packages.base")
3✔
2

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

6
local _odd = true
3✔
7

8
local mirrorMaster = function (_, existing, new)
9
   -- Frames in one master can't "see" frames in another, so we have to get creative
10
   -- XXX This knows altogether too much about the implementation of masters
11
   if not SILE.scratch.masters[new] then
3✔
12
      SILE.scratch.masters[new] = { frames = {} }
3✔
13
   end
14
   if not SILE.scratch.masters[existing] then
3✔
NEW
15
      SU.error("Can't find master " .. existing)
×
16
   end
17
   for name, frame in pairs(SILE.scratch.masters[existing].frames) do
15✔
18
      local newframe = pl.tablex.deepcopy(frame)
12✔
19
      if frame:isAbsoluteConstraint("right") then
24✔
20
         newframe.constraints.left = "100%pw-(" .. frame.constraints.right .. ")"
3✔
21
      end
22
      if frame:isAbsoluteConstraint("left") then
24✔
23
         newframe.constraints.right = "100%pw-(" .. frame.constraints.left .. ")"
3✔
24
      end
25
      SILE.scratch.masters[new].frames[name] = newframe
12✔
26
      if frame == SILE.scratch.masters[existing].firstContentFrame then
12✔
27
         SILE.scratch.masters[new].firstContentFrame = newframe
3✔
28
      end
29
   end
30
end
31

32
function package.oddPage (_)
3✔
33
   return _odd
53✔
34
end
35

36
local function switchPage (class)
37
   _odd = not class:oddPage()
30✔
38
   local nextmaster = _odd and class.oddPageMaster or class.evenPageMaster
15✔
39
   class:switchMaster(nextmaster)
15✔
40
end
41

42
local _deprecate = [[
43
  Directly calling master switch handling functions is no longer necessary. All
44
  the SILE core classes and anything inheriting from them will take care of this
45
  automatically using hooks. Custom classes that override the class:newPage()
46
  function may need to handle this in other ways. By calling this hook directly
47
  you are likely causing it to run twice and duplicate entries.
48
]]
3✔
49

50
local spread_counter = 0
3✔
51
local spreadHook = function ()
52
   spread_counter = spread_counter + 1
15✔
53
end
54

55
function package:_init (options)
3✔
56
   base._init(self)
3✔
57
   if not SILE.scratch.masters then
3✔
NEW
58
      SU.error("Cannot load twoside package before masters.")
×
59
   end
60
   self:export("oddPage", self.oddPage)
3✔
61
   self:export("mirrorMaster", mirrorMaster)
3✔
62
   self:export("switchPage", function ()
6✔
NEW
63
      SU.deprecated("class:switchPage", nil, "0.13.0", "0.15.0", _deprecate)
×
64
   end)
65
   self.class.oddPageMaster = options.oddPageMaster
3✔
66
   self.class.evenPageMaster = options.evenPageMaster
3✔
67
   self.class:registerPostinit(function (class)
6✔
68
      -- TODO: Refactor this to make mirroring a separate package / option
69
      if not SILE.scratch.masters[options.evenPageMaster] then
3✔
70
         class:mirrorMaster(options.oddPageMaster, options.evenPageMaster)
3✔
71
      end
72
   end)
73
   self.class:registerHook("newpage", spreadHook)
3✔
74
   self.class:registerHook("newpage", switchPage)
3✔
75
end
76

77
function package:registerCommands ()
3✔
78
   self:registerCommand("open-double-page", function ()
6✔
79
      SILE.call("open-spread", { double = false, odd = true, blank = false })
3✔
80
   end)
81

82
   self:registerCommand("open-spread-eject", function ()
6✔
83
      SILE.call("supereject")
9✔
84
   end)
85

86
   -- This is upstreamed from CaSILE. Similar to the original open-double-page,
87
   -- but can disable headers and folios on blank pages and allows opening the
88
   -- even side (with or without a leading blank).
89
   self:registerCommand("open-spread", function (options)
6✔
90
      local odd = SU.boolean(options.odd, true)
7✔
91
      local double = SU.boolean(options.double, true)
7✔
92
      local blank = SU.boolean(options.blank, true)
7✔
93
      local optionsMet = function ()
94
         return (not double or spread_counter > 1) and (odd == self.class:oddPage())
21✔
95
      end
96
      spread_counter = 0
7✔
97
      SILE.typesetter:leaveHmode()
7✔
98
      -- Output a box, then remove it and see where we are. Without adding
99
      -- content we can't prove on which page we would land because the page
100
      -- breaker *might* be stuffed almost full but still sitting on the last
101
      -- line happy to *maybe* accept more letter (but not a line). If this check
102
      -- gets us to the desired page nuke the vertical space so we don't leak it
103
      -- into the final content, otherwise just leave it be since we want to be
104
      -- forced to the next page anyway.
105
      SILE.call("hbox")
7✔
106
      SILE.typesetter:leaveHmode()
7✔
107
      table.remove(SILE.typesetter.state.nodes)
7✔
108
      if spread_counter == 1 and optionsMet() then
10✔
109
         SILE.typesetter.state.outputQueue = {}
1✔
110
         return
1✔
111
      end
112
      local startedattop = #SILE.typesetter.state.outputQueue == 2 and #SILE.typesetter.state.nodes == 0
6✔
113
      local spread_counter_at_start = spread_counter
6✔
114
      repeat
115
         if spread_counter > 0 then
9✔
116
            SILE.call("hbox")
5✔
117
            SILE.typesetter:leaveHmode()
5✔
118
            -- Note: before you think you can simplify this, make sure all the
119
            -- pages before chapter starts in the manual have headers if they have
120
            -- content and not if empty. Combined with the workaround for just
121
            -- barely full pages above it's tricky to get right.
122
            if blank and not (spread_counter == spread_counter_at_start and not startedattop) then
5✔
123
               SILE.scratch.headers.skipthispage = true
3✔
124
               SILE.call("nofoliothispage")
3✔
125
            end
126
         end
127
         SILE.call("open-spread-eject")
9✔
128
         SILE.typesetter:leaveHmode()
9✔
129
      until optionsMet()
18✔
130
   end)
131
end
132

133
package.documentation = [[
134
\begin{document}
135
A book-like class usually sets up left and right mirrored page masters.
136
The \autodoc:package{twoside} package is then responsible for swapping between the two left and right frames, running headers, and so on.
137
As it is normally loaded and initialized by a document class, its main function in mirroring master frames does not provide any user-serviceable parts.
138
It does supply a few user-facing commands for convenience.
139

140
The \autodoc:command{\open-double-page} ejects whatever page is currently being processed, then checks if it landed on an even page.
141
If so, it ejects another page to assure content starts on an odd page.
142

143
The \autodoc:command{\open-spread} is similar but a bit more tailored to use in book layouts.
144
By default, headers and folios will be suppressed automatically on any empty pages ejected, making them blank.
145
It can also accept three parameters.
146
The \autodoc:parameter{odd} parameter (default \code{true}) can be used to disable the opening page being odd, hence opening an even page spread.
147
The \autodoc:parameter{double} parameter (default \code{true}) can be used to always output at least one empty even page before the starting an odd page.
148
The \autodoc:parameter{blank} parameter (default \code{true}) can be used to not suppress headers and folios on otherwise empty pages.
149

150
Lastly the \autodoc:command{\open-spread-eject} command can be overridden to customize the output of blank pages.
151
By default it just runs \autodoc:command{\supereject}, but you could potentially add decorative content or other features in the otherwise empty space.
152
\end{document}
153
]]
3✔
154

155
return package
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