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

sile-typesetter / sile / 8288578143

14 Mar 2024 09:39PM UTC coverage: 64.155% (-10.6%) from 74.718%
8288578143

Pull #1904

github

alerque
chore(core): Fixup ec6ed657 which didn't shim old pack styles properly
Pull Request #1904: Merge develop into master (commit to next release being breaking)

1648 of 2421 new or added lines in 107 files covered. (68.07%)

1843 existing lines in 77 files now uncovered.

10515 of 16390 relevant lines covered (64.15%)

3306.56 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
NEW
61
  return bsratio * SILE.types.measurement("1bs"):tonumber()
×
62
end
63

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

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

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

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

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

UNCOV
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