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

sile-typesetter / sile / 14958605596

11 May 2025 06:34PM UTC coverage: 31.311% (-25.4%) from 56.689%
14958605596

push

github

web-flow
Merge 3e53926d5 into 443551a3e

6301 of 20124 relevant lines covered (31.31%)

4203.99 hits per line

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

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

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

6
local metrics = require("fontmetrics")
×
7

8
local metricscache = {}
×
9

10
local getLineMetrics = function (l)
11
   local linemetrics = { ascender = 0, descender = 0, lineheight = SILE.types.length() }
×
12
   if not l or not l.nodes then
×
13
      return linemetrics
×
14
   end
15
   for i = 1, #l.nodes do
×
16
      local node = l.nodes[i]
×
17
      if node.is_nnode then
×
18
         local m = metricscache[SILE.font._key(node.options)]
×
19
         if not m then
×
20
            local face = SILE.font.cache(node.options, SILE.shaper.getFace)
×
21
            m = metrics.get_typographic_extents(face)
×
22
            m.ascender = m.ascender * node.options.size
×
23
            m.descender = m.descender * node.options.size
×
24
            metricscache[SILE.font._key(node.options)] = m
×
25
         end
26
         SILE.settings:temporarily(function ()
×
27
            SILE.call("font", node.options, {})
×
28
            m.lineheight = SU.cast("length", SILE.settings:get("linespacing.css.line-height")):absolute()
×
29
         end)
30
         if m.ascender > linemetrics.ascender then
×
31
            linemetrics.ascender = m.ascender
×
32
         end
33
         if m.descender > linemetrics.descender then
×
34
            linemetrics.descender = m.descender
×
35
         end
36
         if m.lineheight > linemetrics.lineheight then
×
37
            linemetrics.lineheight = m.lineheight
×
38
         end
39
      end
40
   end
41
   return linemetrics
×
42
end
43

44
local linespacingLeading = function (_, vbox, previous)
45
   local method = SILE.settings:get("linespacing.method")
×
46

47
   local firstline = SILE.settings:get("linespacing.minimumfirstlineposition"):absolute()
×
48
   if not previous then
×
49
      if firstline.length:tonumber() > 0 then
×
50
         local toAdd = SILE.types.length(firstline.length - vbox.height)
×
51
         return SILE.types.node.vkern(toAdd)
×
52
      else
53
         return nil
×
54
      end
55
   end
56

57
   if method == "tex" then
×
58
      return SILE.typesetters.base:leadingFor(vbox, previous)
×
59
   end
60

61
   if method == "fit-glyph" then
×
62
      local extra = SILE.settings:get("linespacing.fit-glyph.extra-space"):absolute()
×
63
      local toAdd = SILE.types.length(extra)
×
64
      return SILE.types.node.vglue(toAdd)
×
65
   end
66

67
   if method == "fixed" then
×
68
      local btob = SILE.settings:get("linespacing.fixed.baselinedistance"):absolute()
×
69
      local toAdd = SILE.types.length(btob.length - (vbox.height + previous.depth), btob.stretch, btob.shrink)
×
70
      return SILE.types.node.vglue(toAdd)
×
71
   end
72

73
   -- For these methods, we need to read the font metrics
74
   if not metrics then
×
75
      SU.error("'" .. method .. "' line spacing method requires font metrics module, which is not available")
×
76
   end
77

78
   local thismetrics = getLineMetrics(vbox)
×
79
   local prevmetrics = getLineMetrics(previous)
×
80
   if method == "fit-font" then
×
81
      -- Distance to next baseline is max(descender) of fonts on previous +
82
      -- max(ascender) of fonts on next
83
      local extra = SILE.settings:get("linespacing.fit-font.extra-space"):absolute()
×
84
      local btob = prevmetrics.descender + thismetrics.ascender + extra
×
85
      local toAdd = btob - (vbox.height + (previous and previous.depth or 0))
×
86
      return SILE.types.node.vglue(toAdd)
×
87
   end
88

89
   if method == "css" then
×
90
      local lh = prevmetrics.lineheight
×
91
      local leading = (lh - (prevmetrics.ascender + prevmetrics.descender))
×
92
      if previous then
×
93
         previous.height = previous.height + leading / 2
×
94
         previous.depth = previous.depth + leading / 2
×
95
      end
96
      return SILE.types.node.vglue()
×
97
   end
98

99
   SU.error("Unknown line spacing method " .. method)
×
100
end
101

102
function package:_init ()
×
103
   base._init(self)
×
104
   self.class:registerPostinit(function (_)
×
105
      SILE.typesetter.leadingFor = linespacingLeading
×
106
   end)
107
end
108

109
function package:declareSettings ()
×
110
   SILE.settings:declare({
×
111
      parameter = "linespacing.method",
112
      default = "tex",
113
      type = "string",
114
      help = "How to set the line spacing (tex, fixed, fit-font, fit-glyph, css)",
115
   })
116

117
   SILE.settings:declare({
×
118
      parameter = "linespacing.fixed.baselinedistance",
119
      default = SILE.types.length("1.2em"),
120
      type = "length",
121
      help = "Distance from baseline to baseline in the case of fixed line spacing",
122
   })
123

124
   SILE.settings:declare({
×
125
      parameter = "linespacing.minimumfirstlineposition",
126
      default = SILE.types.length(0),
127
      type = "length",
128
   })
129

130
   SILE.settings:declare({
×
131
      parameter = "linespacing.fit-glyph.extra-space",
132
      default = SILE.types.length(0),
133
      type = "length",
134
   })
135

136
   SILE.settings:declare({
×
137
      parameter = "linespacing.fit-font.extra-space",
138
      default = SILE.types.length(0),
139
      type = "length",
140
   })
141

142
   SILE.settings:declare({
×
143
      parameter = "linespacing.css.line-height",
144
      default = SILE.types.length("1.2em"),
145
      type = "length",
146
   })
147
end
148

149
function package:registerCommands ()
×
150
   self:registerCommand("linespacing-on", function ()
×
151
      SILE.typesetter.leadingFor = linespacingLeading
×
152
   end)
153

154
   self:registerCommand("linespacing-off", function ()
×
155
      SILE.typesetter.leadingFor = SILE.typesetters.base.leadingFor
×
156
   end)
157
end
158

159
package.documentation = [[
160
\begin{document}
161
\linespacing-on
162
SILE’s default method of inserting leading between lines should be familiar to users of TeX, but it is not the most friendly system for book designers.
163
The \autodoc:package{linespacing} package provides a better choice of leading systems.
164

165
After loading the package, you are able to choose the linespacing mode by setting the \autodoc:setting{linespacing.method} parameter.
166
The following examples have funny sized words in them so that you can see how the different methods interact.
167

168
By default, this is set to \code{tex}. The other options available are:
169

170
\medskip
171
\set[parameter=linespacing.method,value=fixed]
172
\set[parameter=linespacing.fixed.baselinedistance,value=1.5em]
173
\begin{itemize}
174
\item{\code{fixed}. This set the lines at a fixed baseline-to-baseline distance, determined by the \autodoc:setting{linespacing.fixed.baselinedistance} parameter.
175
You can specify this parameter either relative to the type size (\code{1.2em}) or as a absolute distance (\code{15pt}).
176
This paragraph is set with a fixed 1.5em baseline-to-baseline distance.}
177
\end{itemize}
178

179
\medskip
180
\set[parameter=linespacing.method,value=fit-glyph]
181
\begin{itemize}
182
\item{\code{fit-glyph}. This sets the lines solid; that is, the lowest point on line 1 (either a descender like \font[size=20pt]{q} or, if there are no descenders, the baseline) will touch the \font[size=20pt]{highest} point of line 2, as in this paragraph.
183
You generally don’t want to use this as-is.}
184
\end{itemize}
185

186
\set[parameter=linespacing.fit-glyph.extra-space,value=5pt]
187

188
What you probably want to do is insert a constant (relative or absolute) s\font[size=20pt]{p}ace between the lines by setting the \autodoc:setting{linespacing.fit-glyph.extra-space} parameter.
189
\font[size=20pt]{T}his paragraph is set with 5 points of space between the descenders and the ascenders.
190

191
\medskip
192
\set[parameter=linespacing.method,value=fit-font]
193
\begin{itemize}
194
\item{\code{fit-font}. This inspects each \code{hbox} on the line, and asks the fonts it finds for their bounding boxes—the highest ascender and the lower descender.
195
It then sets the lines solid.
196
Essentially each character is treated as if it is the same height, rather like composing a slug of metal type.
197
If there are things other than text on your line, or the text is buried inside other boxes, this may not work well.}
198
\end{itemize}
199

200
\set[parameter=linespacing.fit-font.extra-space,value=5pt]
201

202
As with \code{fit-glyph}, you can insert extra space between the lines with the \autodoc:setting{linespacing.fit-font.extra-space} parameter.
203

204
\medskip
205
\set[parameter=linespacing.method,value=css]
206
\set[parameter=linespacing.css.line-height,value=2em]
207
\begin{itemize}
208
\item{\code{css}. This is similar to the method used in browsers; the baseline distance is set with the \autodoc:setting{linespacing.css.line-height} parameter, and the excess \font[size=20pt]{space} between this parameter and the actual height of the line is distributed between the top and bottom of the line.}
209
\end{itemize}
210
\medskip
211

212
\linespacing-off
213
\end{document}
214
]]
×
215

216
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

© 2026 Coveralls, Inc