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

sile-typesetter / sile / 8736205729

18 Apr 2024 10:16AM UTC coverage: 63.747% (-11.0%) from 74.718%
8736205729

push

github

alerque
chore(classes): Correct typos in user facing deprecation message

10118 of 15872 relevant lines covered (63.75%)

6751.63 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.measurement("1bs"):tonumber()
×
62
end
63

64
function package:registerCommands ()
×
65

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

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

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

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

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

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

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

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

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

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

162
end
163

164
package.documentation = [[
165
\begin{document}
166
\use[module=packages.dropcaps]
167
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.
168

169
It provides the \autodoc:command{\dropcap} command.
170
The content passed will be the initial character(s).
171
The primary option is \autodoc:parameter{lines}, an integer specifying the number of lines to span (defaults to \code{3}).
172
The scale of the characters can be adjusted relative to the first line using the \autodoc:parameter{scale} option (defaults to \code{1.0}).
173
The \autodoc:parameter{join} parameter is a boolean for whether to join the dropcap to the first line (defaults to \code{false}).
174
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.
175
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.
176
To tweak the position of the dropcap, measurements may be passed to the \autodoc:parameter{raise} and \autodoc:parameter{shift} options.
177
Other options passed to \autodoc:command{\dropcap} will be passed through to \autodoc:command{\font} when drawing the initial letter(s).
178
This may be useful for passing OpenType options or other font preferences.
179

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

183
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.
184
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.
185
This tolerance is computed based on the font metrics.
186
If you want to bypass this mechanism and adjust the tolerance, you can use the \autodoc:setting{dropcaps.bsratio} setting.
187

188
Moreover, some fonts, such as EB Garamond Initials, have \em{all} capitals hanging below the baseline.
189
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}.
190
The character(s) used for this depth adjustment correction can be specified using the \autodoc:parameter{depthadjust} option.
191

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

202
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