From 9a92ae08707285cebd2db021e6bf7810bb4af2fb Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sun, 17 Dec 2023 06:36:55 +0100 Subject: [PATCH] fixup! fix(packages): Adjust dropcap logic for letters with a depth --- packages/dropcaps/init.lua | 64 +++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/packages/dropcaps/init.lua b/packages/dropcaps/init.lua index c5ea3f98e..271dd9ef4 100644 --- a/packages/dropcaps/init.lua +++ b/packages/dropcaps/init.lua @@ -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) @@ -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. @@ -63,18 +94,18 @@ 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) @@ -82,13 +113,14 @@ function package:registerCommands () -- 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) @@ -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.