• 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

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

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

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

12
function package.declareSettings (_)
×
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)
22
   local hbox = SILE.typesetter:makeHbox(function ()
×
23
      SILE.call("font", options, content)
×
24
   end)
25
   return hbox
×
26
end
27

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

31
local computeBaselineRatio = function ()
32
   local fontoptions = SILE.font.loadDefaults({})
×
33
   local bsratio = bsratiocache[SILE.font._key(fontoptions)]
×
34
   if not bsratio then
×
35
      local face = SILE.font.cache(fontoptions, SILE.shaper.getFace)
×
36
      local m = metrics.get_typographic_extents(face)
×
37
      bsratio = m.descender / (m.ascender + m.descender)
×
38
      bsratiocache[SILE.font._key(fontoptions)] = bsratio
×
39
   end
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
54
   if SILE.settings:get("dropcaps.bsratio") then
×
55
      bsratio = SILE.settings:get("dropcaps.bsratio")
×
56
      SU.debug("dropcaps", "Using user-defined descender baseline ratio", bsratio)
×
57
   else
58
      bsratio = computeBaselineRatio()
×
59
      SU.debug("dropcaps", "Using computed descender baseline ratio", bsratio)
×
60
   end
61
   return bsratio * SILE.types.measurement("1bs"):tonumber()
×
62
end
63

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.
67
   self:registerCommand("dropcap", function (options, content)
×
68
      local lines = SU.cast("integer", options.lines or 3)
×
69
      local join = SU.boolean(options.join, false)
×
70
      local standoff = SU.cast("measurement", options.standoff or "1spc")
×
71
      local raise = SU.cast("measurement", options.raise or 0)
×
72
      local shift = SU.cast("measurement", options.shift or 0)
×
73
      local size = SU.cast("measurement or nil", options.size or nil)
×
74
      local scale = SU.cast("number", options.scale or 1.0)
×
75
      local strict = SU.boolean(options.strict, true)
×
76
      if strict and options.depthadjust then
×
77
         SU.warn("The depthadjust option is ignored in strict mode.")
×
78
      end
79
      local color = options.color
×
80
      -- We need to measure the "would have been" size before using this.
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 =
×
84
         nil, nil, nil, nil, nil, nil
×
85

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.
94
      local depthadjust = options.depthadjust or "I"
×
95
      local depthAdjustment = not strict and shapeHbox(options, { depthadjust }).depth:tonumber() or 0
×
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.
101
      local tmpHbox = shapeHbox(options, content)
×
102
      local extraHeight = SILE.types.measurement((lines - 1) .. "bs"):tonumber()
×
103
      local curHeight = tmpHbox.height:tonumber() + depthAdjustment
×
104
      local targetHeight = (curHeight - depthAdjustment) * scale + extraHeight
×
105
      if strict then
×
106
         -- Take into account the compensated depth of the initial
107
         curHeight = curHeight + tmpHbox.depth:tonumber()
×
108
      end
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.
113
      local curSize = SILE.types.measurement(SILE.settings:get("font.size")):tonumber()
×
114
      local curWidth = tmpHbox.width:tonumber()
×
115
      options.size = size and size:tonumber() or (targetHeight / curHeight * curSize)
×
116
      local targetWidth = curWidth / curSize * options.size
×
117
      SU.debug("dropcaps", "Target font size", options.size)
×
118
      SU.debug("dropcaps", "Target width", targetWidth)
×
119

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

123
      if not strict then
×
124
         -- Compensation for regular extra depth.
125
         local compensationHeight = depthAdjustment * options.size / curSize
×
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.
130
         local extraDepth = hbox.depth:tonumber() - compensationHeight
×
131
         local toleranceDepth = getToleranceDepth()
×
132
         if extraDepth > toleranceDepth then
×
133
            SU.debug("dropcaps", "Extra depth", extraDepth, "> tolerance", toleranceDepth)
×
134
            local extraLines = math.ceil((extraDepth - toleranceDepth) / SILE.types.measurement("1bs"):tonumber())
×
135
            lines = lines + extraLines
×
136
            SU.debug("dropcaps", "Extra lines needed to fit", extraLines)
×
137
         else
138
            SU.debug("dropcaps", "Extra depth", extraDepth, "< tolerance", toleranceDepth)
×
139
         end
140
         raise = raise:tonumber() + compensationHeight
×
141
      else
142
         raise = raise:tonumber() + hbox.depth:tonumber()
×
143
      end
144

145
      -- Setup up the necessary indents for the final paragraph content
146
      local joinOffset = join and standoff:tonumber() or 0
×
147
      SILE.settings:set("current.hangAfter", -lines)
×
148
      SILE.settings:set("current.hangIndent", targetWidth + joinOffset)
×
149
      SILE.call("noindent")
×
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.
154
      SILE.call("rebox", { height = 0, depth = 0, width = -joinOffset }, function ()
×
155
         SILE.call("glue", { width = shift - targetWidth - joinOffset })
×
156
         SILE.call("lower", { height = extraHeight - raise }, function ()
×
157
            SILE.call(color and "color" or "noop", { color = color }, function ()
×
158
               SILE.typesetter:pushHbox(hbox)
×
159
            end)
160
         end)
161
      end)
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}
201
]]
×
202

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

© 2025 Coveralls, Inc