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

sile-typesetter / sile / 9428435077

08 Jun 2024 11:35AM UTC coverage: 64.56% (-9.9%) from 74.46%
9428435077

push

github

web-flow
Merge pull request #2047 from alerque/end-pars

23 of 46 new or added lines in 5 files covered. (50.0%)

1684 existing lines in 60 files now uncovered.

11145 of 17263 relevant lines covered (64.56%)

4562.45 hits per line

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

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

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

UNCOV
6
function package:_init ()
×
UNCOV
7
   base._init(self)
×
UNCOV
8
   self:loadPackage("rebox")
×
UNCOV
9
   self:loadPackage("raiselower")
×
10
end
11

UNCOV
12
function package.declareSettings (_)
×
UNCOV
13
   SILE.settings:declare({
×
14
      parameter = "dropcaps.bsratio",
15
      type = "number or nil",
16
      default = nil, -- nil means "use computed value based on font metrics"
17
      help = "When set, fixed default ratio of the descender with respect to the baseline (around 0.3 in usual fonts).",
18
   })
19
end
20

21
local function shapeHbox (options, content)
UNCOV
22
   local hbox = SILE.typesetter:makeHbox(function ()
×
UNCOV
23
      SILE.call("font", options, content)
×
24
   end)
UNCOV
25
   return hbox
×
26
end
27

UNCOV
28
local metrics = require("fontmetrics")
×
UNCOV
29
local bsratiocache = {}
×
30

31
local computeBaselineRatio = function ()
UNCOV
32
   local fontoptions = SILE.font.loadDefaults({})
×
UNCOV
33
   local bsratio = bsratiocache[SILE.font._key(fontoptions)]
×
UNCOV
34
   if not bsratio then
×
UNCOV
35
      local face = SILE.font.cache(fontoptions, SILE.shaper.getFace)
×
UNCOV
36
      local m = metrics.get_typographic_extents(face)
×
UNCOV
37
      bsratio = m.descender / (m.ascender + m.descender)
×
UNCOV
38
      bsratiocache[SILE.font._key(fontoptions)] = bsratio
×
39
   end
UNCOV
40
   return bsratio
×
41
end
42

43
local function getToleranceDepth ()
44
   -- In non-strict mode, we allow using more lines to fit the dropcap.
45
   -- However we cannot just check if the "extra depth" of the dropcap is above 0.
46
   -- First, our depth adjustment is but a best attempt.
47
   -- Moreover, some characters may have a small depth of their own (ex. "O" in Gentium Plus)
48
   -- We must just ensure they stay within "reasonable bounds" with respect to the baseline,
49
   -- so as not to flow over the next lines.
50
   -- We compute a tolerance ratio based on the font metrics, expecting the font to be well-designed.
51
   -- The user can override the computation and set the dropcaps.bsratio setting manually.
52
   -- (LaTeX would likely approximate it using a \strut = with a depth ratio of 0.3bs)
53
   local bsratio
UNCOV
54
   if SILE.settings:get("dropcaps.bsratio") then
×
UNCOV
55
      bsratio = SILE.settings:get("dropcaps.bsratio")
×
UNCOV
56
      SU.debug("dropcaps", "Using user-defined descender baseline ratio", bsratio)
×
57
   else
UNCOV
58
      bsratio = computeBaselineRatio()
×
UNCOV
59
      SU.debug("dropcaps", "Using computed descender baseline ratio", bsratio)
×
60
   end
UNCOV
61
   return bsratio * SILE.types.measurement("1bs"):tonumber()
×
62
end
63

UNCOV
64
function package:registerCommands ()
×
65
   -- This implementation relies on the hangafter and hangindent features of Knuth's line-breaking algorithm.
66
   -- These features in core line breaking algorithm supply the blank space in the paragraph shape but don't fill it with anything.
UNCOV
67
   self:registerCommand("dropcap", function (options, content)
×
UNCOV
68
      local lines = SU.cast("integer", options.lines or 3)
×
UNCOV
69
      local join = SU.boolean(options.join, false)
×
UNCOV
70
      local standoff = SU.cast("measurement", options.standoff or "1spc")
×
UNCOV
71
      local raise = SU.cast("measurement", options.raise or 0)
×
UNCOV
72
      local shift = SU.cast("measurement", options.shift or 0)
×
UNCOV
73
      local size = SU.cast("measurement or nil", options.size or nil)
×
UNCOV
74
      local scale = SU.cast("number", options.scale or 1.0)
×
UNCOV
75
      local strict = SU.boolean(options.strict, true)
×
UNCOV
76
      if strict and options.depthadjust then
×
77
         SU.warn("The depthadjust option is ignored in strict mode.")
×
78
      end
UNCOV
79
      local color = options.color
×
80
      -- We need to measure the "would have been" size before using this.
UNCOV
81
      options.size = nil
×
82
      -- Clear irrelevant option values before passing to font and measuring content.
83
      options.lines, options.join, options.raise, options.shift, options.color, options.scale =
×
UNCOV
84
         nil, nil, nil, nil, nil, nil
×
85

UNCOV
86
      if color then
×
87
         self:loadPackage("color")
×
88
      end
89

90
      -- Some initial capital fonts have all their glyphs hanging below the baseline (e.g. EB Garamond Initials)
91
      -- We cannot manage all pathological cases.
92
      -- Quite empirically, we can shape character(s) which shouldn't usually have a depth normally.
93
      -- If it has, then likely all glyphs do also and we need to compensate for that everywhere.
UNCOV
94
      local depthadjust = options.depthadjust or "I"
×
UNCOV
95
      local depthAdjustment = not strict and shapeHbox(options, { depthadjust }).depth:tonumber() or 0
×
UNCOV
96
      SU.debug("dropcaps", "Depth adjustment", depthAdjustment)
×
97

98
      -- We want the drop cap to span over N lines, that is N - 1 baselineskip + the height of the first line.
99
      -- Note this only works for the default linespace mechanism.
100
      -- We determine the height of the first line by measuring the size the initial content *would have* been.
UNCOV
101
      local tmpHbox = shapeHbox(options, content)
×
UNCOV
102
      local extraHeight = SILE.types.measurement((lines - 1) .. "bs"):tonumber()
×
UNCOV
103
      local curHeight = tmpHbox.height:tonumber() + depthAdjustment
×
UNCOV
104
      local targetHeight = (curHeight - depthAdjustment) * scale + extraHeight
×
UNCOV
105
      if strict then
×
106
         -- Take into account the compensated depth of the initial
UNCOV
107
         curHeight = curHeight + tmpHbox.depth:tonumber()
×
108
      end
UNCOV
109
      SU.debug("dropcaps", "Target height", targetHeight)
×
110

111
      -- Now we need to figure out how to scale the dropcap font to get an initial of targetHeight.
112
      -- From that we can also figure out the width it will be at that height.
UNCOV
113
      local curSize = SILE.types.measurement(SILE.settings:get("font.size")):tonumber()
×
UNCOV
114
      local curWidth = tmpHbox.width:tonumber()
×
UNCOV
115
      options.size = size and size:tonumber() or (targetHeight / curHeight * curSize)
×
UNCOV
116
      local targetWidth = curWidth / curSize * options.size
×
UNCOV
117
      SU.debug("dropcaps", "Target font size", options.size)
×
UNCOV
118
      SU.debug("dropcaps", "Target width", targetWidth)
×
119

120
      -- Typeset the dropcap with its final shape, but don't output it yet.
UNCOV
121
      local hbox = shapeHbox(options, content)
×
122

UNCOV
123
      if not strict then
×
124
         -- Compensation for regular extra depth.
UNCOV
125
         local compensationHeight = depthAdjustment * options.size / curSize
×
UNCOV
126
         SU.debug("dropcaps", "Compensation height", compensationHeight)
×
127

128
         -- Some fonts have descenders on letters such as Q, J, etc.
129
         -- In that case we may need extra lines to the dropcap.
UNCOV
130
         local extraDepth = hbox.depth:tonumber() - compensationHeight
×
UNCOV
131
         local toleranceDepth = getToleranceDepth()
×
UNCOV
132
         if extraDepth > toleranceDepth then
×
UNCOV
133
            SU.debug("dropcaps", "Extra depth", extraDepth, "> tolerance", toleranceDepth)
×
UNCOV
134
            local extraLines = math.ceil((extraDepth - toleranceDepth) / SILE.types.measurement("1bs"):tonumber())
×
UNCOV
135
            lines = lines + extraLines
×
UNCOV
136
            SU.debug("dropcaps", "Extra lines needed to fit", extraLines)
×
137
         else
UNCOV
138
            SU.debug("dropcaps", "Extra depth", extraDepth, "< tolerance", toleranceDepth)
×
139
         end
UNCOV
140
         raise = raise:tonumber() + compensationHeight
×
141
      else
UNCOV
142
         raise = raise:tonumber() + hbox.depth:tonumber()
×
143
      end
144

145
      -- Setup up the necessary indents for the final paragraph content
UNCOV
146
      local joinOffset = join and standoff:tonumber() or 0
×
UNCOV
147
      SILE.settings:set("current.hangAfter", -lines)
×
UNCOV
148
      SILE.settings:set("current.hangIndent", targetWidth + joinOffset)
×
UNCOV
149
      SILE.call("noindent")
×
UNCOV
150
      SU.debug("dropcaps", "joinOffset", joinOffset)
×
151

152
      -- The paragraph is indented so as to leave enough space for the drop cap.
153
      -- We "trick" the typesetter with a zero-dimension box wrapping our original box.
UNCOV
154
      SILE.call("rebox", { height = 0, depth = 0, width = -joinOffset }, function ()
×
UNCOV
155
         SILE.call("glue", { width = shift - targetWidth - joinOffset })
×
UNCOV
156
         SILE.call("lower", { height = extraHeight - raise }, function ()
×
UNCOV
157
            SILE.call(color and "color" or "noop", { color = color }, function ()
×
UNCOV
158
               SILE.typesetter:pushHbox(hbox)
×
159
            end)
160
         end)
161
      end)
UNCOV
162
   end, "Show an 'initial capital' (also known as a 'drop cap') at the start of the content paragraph.")
×
163
end
164

165
package.documentation = [[
166
\begin{document}
167
\use[module=packages.dropcaps]
168
The \autodoc:package{dropcaps} package allows you to format paragraphs with an “initial capital” (also commonly referred as a “drop cap”), typically one large capital letter used as a decorative element at the beginning of a paragraph.
169

170
It provides the \autodoc:command{\dropcap} command.
171
The content passed will be the initial character(s).
172
The primary option is \autodoc:parameter{lines}, an integer specifying the number of lines to span (defaults to \code{3}).
173
The scale of the characters can be adjusted relative to the first line using the \autodoc:parameter{scale} option (defaults to \code{1.0}).
174
The \autodoc:parameter{join} parameter is a boolean for whether to join the dropcap to the first line (defaults to \code{false}).
175
If \autodoc:parameter{join} is \code{true}, the value of the \autodoc:parameter{standoff} option (defaults to \code{1spc}) is applied to all but the first line.
176
Optionally \autodoc:parameter{color} can be passed to change the typeface color, which is sometimes useful to offset the apparent weight of a large glyph.
177
To tweak the position of the dropcap, measurements may be passed to the \autodoc:parameter{raise} and \autodoc:parameter{shift} options.
178
Other options passed to \autodoc:command{\dropcap} will be passed through to \autodoc:command{\font} when drawing the initial letter(s).
179
This may be useful for passing OpenType options or other font preferences.
180

181
Some fonts have capitals — such as, typically, \autodoc:example{Q} and \autodoc:example{J} — hanging below the baseline.
182
By default, the dropcap fits the specified number of lines and the characters are typeset in a smaller size to fit these descenders.
183

184
With the \autodoc:parameter{strict=false} option, the characters are scaled with respect to their height only, and extra hanged lines are added to the dropcap in order to accommodate the descenders.
185
The dropcap is allowed to overflow the baseline by a reasonable amount, before triggering the addition of extra lines, for fonts that have capitals very slightly hanging below the baseline.
186
This tolerance is computed based on the font metrics.
187
If you want to bypass this mechanism and adjust the tolerance, you can use the \autodoc:setting{dropcaps.bsratio} setting.
188

189
Moreover, some fonts, such as EB Garamond Initials, have \em{all} capitals hanging below the baseline.
190
To take this case into account in non-strict mode, the depth adjustment of the dropcap is empirically corrected based on that of a character which shouldn't have any, by default an \autodoc:example{I}.
191
The character(s) used for this depth adjustment correction can be specified using the \autodoc:parameter{depthadjust} option.
192

193
\begin{autodoc:note}
194
One caveat is that the size of the initials is calculated using the default linespacing mechanism.
195
If you are using an alternative method from the \autodoc:package{linespacing} package, you might see strange results.
196
Set the \autodoc:setting{document.baselineskip} to approximate your effective leading value for best results.
197
If that doesn't work set the size manually.
198
Using \code{SILE.setCommandDefaults()} can be helpful for so you don't have to set the size every time.
199
\end{autodoc:note}
200
\end{document}
UNCOV
201
]]
×
202

UNCOV
203
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