Skip to content

Commit

Permalink
fixup! fix(packages): Adjust dropcap logic for letters with a depth
Browse files Browse the repository at this point in the history
  • Loading branch information
Omikhleia authored and Didier Willis committed Dec 17, 2023
1 parent 1240c77 commit 9a92ae0
Showing 1 changed file with 50 additions and 14 deletions.
64 changes: 50 additions & 14 deletions packages/dropcaps/init.lua
Expand Up @@ -7,18 +7,49 @@ function package:_init ()
base._init(self)
self:loadPackage("rebox")
self:loadPackage("raiselower")
self:loadPackage("rules")
end

function package.declareSettings (_)
SILE.settings:declare({
parameter = "dropcaps.bsratio",
type = "number or nil",
default = nil,
help = "Default ratio of the descender to the baseline (around 0.3 in usual fonts)."
})
end

local metrics = require("fontmetrics")
local metricscache = {}
local getMetrics = function (l)
local fontoptions = SILE.font.loadDefaults({})
local m = metricscache[SILE.font._key(fontoptions)]
if not m then
local face = SILE.font.cache(fontoptions, SILE.shaper.getFace)
m = metrics.get_typographic_extents(face)
m.ascender = m.ascender
m.descender = m.descender
metricscache[SILE.font._key(fontoptions)] = m
end
return m
end

local function getToleranceDepth ()
-- In non-strict mode, we allow using more lines to fit the dropcap.
-- However we cannot just check if the extra depth of the dropcap is above 0.
-- However we cannot just check if the "extra depth" of the dropcap is above 0.
-- - our depth adjustment is but a best attempt.
-- - Some characters may have a small depth of their own, such as the "O" in Gentium Plus.
-- We must just ensure they stay within reasonable bounds with respect to the baseline,
-- so as not to flow over the next lines.
-- With well-formed text font, a good compromise is around 0.3bs (LaTeX does this too for defining its \strut).
-- This could be computed more precisely (e.g. computing the ratio between ascender and descender font metrics).
return SILE.measurement("0.3bs"):tonumber()
-- We compute a tolerance ratio based on the font metrics.
-- If the doesn't work, the user can still set the dropcaps.bsratio setting.
-- With well-formed text font, a good compromise is around 0.3 (LaTeX does this too for defining its \strut).
if SILE.settings:get("dropcaps.bsratio") then
return SILE.settings.get("dropcaps.bsratio") * SILE.measurement("1bs"):tonumber()
end
local m = getMetrics()
local descenderRatio = m.descender / (m.ascender + m.descender)
return descenderRatio * SILE.measurement("1bs"):tonumber()
end

local shapeHbox = function (options, content)
Expand Down Expand Up @@ -52,9 +83,9 @@ function package:registerCommands ()

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

-- We want the drop cap to span over N lines, that is N - 1 baselineskip + the height of the first line.
Expand All @@ -63,32 +94,33 @@ function package:registerCommands ()
local tmpHbox = shapeHbox(options, content)
local extraHeight = SILE.measurement((lines - 1).."bs"):tonumber()
local curHeight = tmpHbox.height:tonumber() + depthAdjustment
local targetHeight = (curHeight - depthAdjustment) * scale + extraHeight
if strict then
-- Take into account the compensated depth of the initial
curHeight = curHeight + tmpHbox.depth:tonumber() - depthAdjustment
curHeight = curHeight + tmpHbox.depth:tonumber()
end
local targetHeight = curHeight * scale + extraHeight
SU.debug("dropcaps", "Target height", targetHeight)

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

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

-- Compensation for regular extra depth.
local compensationHeight = depthAdjustment * options.size / curSize

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

-- Some fonts have descenders on letters such as Q, J, etc.
-- In that case we may need extra lines to the dropcap.
local extraDepth = hbox.depth:tonumber() - compensationHeight - raise:tonumber()
local extraDepth = hbox.depth:tonumber() - compensationHeight
local toleranceDepth = getToleranceDepth()
if extraDepth > toleranceDepth then
SU.debug("dropcaps", "Extra depth", extraDepth, "> tolerance", toleranceDepth)
Expand Down Expand Up @@ -141,11 +173,15 @@ This may be useful for passing OpenType options or other font preferences.
Some fonts have capitals — such as, typically, Q and J — hanging below the baseline.
By default, the dropcap fits the specified number of line and the characters are typeset in a smaller size to fit these descenders.
With the \autodoc:parameter{strict} option set to \code{false}, the characters are scaled with respect to their height only, and extra hanged lines are added to the dropcap to accommodate the descenders.
In that case, some fonts have capitals very slightly hanging below the baseline.
The dropcap is allowed to overflow the baseline by a reasonable amount, before triggering the addition of extra lines.
This tolerance is computed based on the font metrics.
If you want to bypass this mechanism and adjust the tolerance, you can use the \autodoc:setting{dropcaps.bsratio} setting.
Some fonts, such as EB Garamond Initials, have \em{all} capitals hanging below the baseline.
For the latter, you should let the \autodoc:parameter{strict} option to its default (\code{true}).
The \autodoc:parameter{depthadjust=true} option empirically adjusts the dropcap depth based of that of the X character.
The \autodoc:parameter{depthadjust=true} option empirically adjusts the dropcap depth based of that of the I character.
Combined with \autodoc:parameter{strict=false}, it will work for EB Garamond Initials too.
While it is not possible to handle all pathological cases, these two options may help in some cases.
Expand Down

0 comments on commit 9a92ae0

Please sign in to comment.