local base = require("packages.base")
local package = pl.class(base)
package._name = "rules"
local function getUnderlineParameters ()
local ot = require("core.opentype-parser")
local fontoptions = SILE.font.loadDefaults({})
local face = SILE.font.cache(fontoptions, SILE.shaper.getFace)
local font = ot.parseFont(face)
local upem = font.head.unitsPerEm
local underlinePosition = font.post.underlinePosition / upem * fontoptions.size
local underlineThickness = font.post.underlineThickness / upem * fontoptions.size
return underlinePosition, underlineThickness
end
local function getStrikethroughParameters ()
local ot = require("core.opentype-parser")
local fontoptions = SILE.font.loadDefaults({})
local face = SILE.font.cache(fontoptions, SILE.shaper.getFace)
local font = ot.parseFont(face)
local upem = font.head.unitsPerEm
local yStrikeoutPosition = font.os2.yStrikeoutPosition / upem * fontoptions.size
local yStrikeoutSize = font.os2.yStrikeoutSize / upem * fontoptions.size
return yStrikeoutPosition, yStrikeoutSize
end
local hrulefillglue = pl.class(SILE.types.node.hfillglue)
hrulefillglue.raise = SILE.types.measurement()
hrulefillglue.thickness = SILE.types.measurement("0.2pt")
function hrulefillglue:outputYourself (typesetter, line)
local outputWidth = SU.rationWidth(self.width, self.width, line.ratio):tonumber()
local oldx = typesetter.frame.state.cursorX
typesetter.frame:advancePageDirection(-self.raise)
typesetter.frame:advanceWritingDirection(outputWidth)
local newx = typesetter.frame.state.cursorX
local newy = typesetter.frame.state.cursorY
SILE.outputter:drawRule(oldx, newy, newx - oldx, self.thickness)
typesetter.frame:advancePageDirection(self.raise)
end
function package:_init ()
base._init(self)
self:loadPackage("raiselower")
self:loadPackage("rebox")
end
function package:registerCommands ()
self:registerCommand("hrule", function (options, _)
local width = SU.cast("length", options.width)
local height = SU.cast("length", options.height)
local depth = SU.cast("length", options.depth)
SILE.typesetter:pushHbox({
width = width:absolute(),
height = height:absolute(),
depth = depth:absolute(),
value = options.src,
outputYourself = function (node, typesetter, line)
local outputWidth = SU.rationWidth(node.width, node.width, line.ratio)
typesetter.frame:advancePageDirection(-node.height)
local oldx = typesetter.frame.state.cursorX
local oldy = typesetter.frame.state.cursorY
typesetter.frame:advanceWritingDirection(outputWidth)
typesetter.frame:advancePageDirection(node.height + node.depth)
local newx = typesetter.frame.state.cursorX
local newy = typesetter.frame.state.cursorY
SILE.outputter:drawRule(oldx, oldy, newx - oldx, newy - oldy)
typesetter.frame:advancePageDirection(-node.depth)
end,
})
end, "Draws a blob of ink of width <width>, height <height> and depth <depth>")
self:registerCommand("hrulefill", function (options, _)
local raise
local thickness
if options.position and options.raise then
SU.error("hrulefill cannot have both position and raise parameters")
end
if options.thickness then
thickness = SU.cast("measurement", options.thickness)
end
if options.position == "underline" then
local underlinePosition, underlineThickness = getUnderlineParameters()
thickness = thickness or underlineThickness
raise = underlinePosition
elseif options.position == "strikethrough" then
local yStrikeoutPosition, yStrikeoutSize = getStrikethroughParameters()
thickness = thickness or yStrikeoutSize
raise = yStrikeoutPosition + thickness / 2
elseif options.position then
SU.error("Unknown hrulefill position '" .. options.position .. "'")
else
raise = SU.cast("measurement", options.raise or "0")
end
SILE.typesetter:pushExplicitGlue(hrulefillglue({
raise = raise,
thickness = thickness or SILE.types.measurement("0.2pt"),
}))
end, "Add a huge horizontal hrule glue")
self:registerCommand("fullrule", function (options, _)
local thickness = SU.cast("measurement", options.thickness or "0.2pt")
local raise = SU.cast("measurement", options.raise or "0.5em")
if options.height then
SU.deprecated("\\fullrule[…, height=…]", "\\fullrule[…, thickness=…]", "0.13.1", "0.15.0")
end
if not SILE.typesetter:vmode() then
SU.deprecated("\\fullrule in horizontal mode", "\\hrule or \\hrulefill", "0.13.1", "0.15.0")
end
if options.width then
SU.deprecated("\\fullrule with width", "\\hrule and \\raise", "0.13.1 ", "0.15.0")
end
SILE.typesetter:leaveHmode()
SILE.call("noindent")
SILE.call("hrulefill", { raise = raise, thickness = thickness })
SILE.typesetter:leaveHmode()
end, "Draw a full width hrule centered on the current line")
self:registerCommand("underline", function (_, content)
local underlinePosition, underlineThickness = getUnderlineParameters()
SILE.typesetter:liner("underline", content, function (box, typesetter, line)
local oldX = typesetter.frame.state.cursorX
local Y = typesetter.frame.state.cursorY
box:outputContent(typesetter, line)
local newX = typesetter.frame.state.cursorX
SILE.outputter:drawRule(oldX, Y - underlinePosition, newX - oldX, underlineThickness)
end)
end, "Underlines some content")
self:registerCommand("strikethrough", function (_, content)
local yStrikeoutPosition, yStrikeoutSize = getStrikethroughParameters()
SILE.typesetter:liner("strikethrough", content, function (box, typesetter, line)
local oldX = typesetter.frame.state.cursorX
local Y = typesetter.frame.state.cursorY
box:outputContent(typesetter, line)
local newX = typesetter.frame.state.cursorX
SILE.outputter:drawRule(oldX, Y - yStrikeoutPosition - yStrikeoutSize / 2, newX - oldX, yStrikeoutSize)
end)
end, "Strikes out some content")
self:registerCommand("boxaround", function (_, content)
SU.deprecated("\\boxaround (undocumented)", "\\framebox (package)", "0.12.0")
local hbox, hlist = SILE.typesetter:makeHbox(content)
SILE.typesetter:pushHbox({
inner = hbox,
width = hbox.width,
height = hbox.height,
depth = hbox.depth,
outputYourself = function (node, typesetter, line)
local oldX = typesetter.frame.state.cursorX
local Y = typesetter.frame.state.cursorY
node.inner:outputYourself(SILE.typesetter, line)
local newX = typesetter.frame.state.cursorX
local w = newX - oldX
local h = node.height:tonumber()
local d = node.depth:tonumber()
local thickness = 0.5
SILE.outputter:drawRule(oldX, Y + d - thickness, w, thickness)
SILE.outputter:drawRule(oldX, Y - h, w, thickness)
SILE.outputter:drawRule(oldX, Y - h, thickness, h + d)
SILE.outputter:drawRule(oldX + w - thickness, Y - h, thickness, h + d)
end,
})
SILE.typesetter:pushHlist(hlist)
end, "Draws a box around some content")
end
package.documentation = [[
\begin{document}
The \autodoc:package{rules} package provides several line-drawing commands.
The \autodoc:command{\hrule} command draws a blob of ink of a given \autodoc:parameter{width} (length), \autodoc:parameter{height} (above the current baseline), and \autodoc:parameter{depth} (below the current baseline).
Such rules are horizontal boxes, placed along the baseline of a line of text and treated just like other text to be output.
So, they can appear in the middle of a paragraph, like this:
\hrule[width=20pt, height=0.5pt]
(That one was generated with \autodoc:command{\hrule[width=20pt, height=0.5pt]}.)
The \autodoc:command{\underline} command \underline{underlines} its content.
The \autodoc:command{\strikethrough} command \strikethrough{strikes} its content.
Both commands support paragraph content spanning multiple lines.
\autodoc:note{The position and thickness of the underlines and strikethroughs are based on the metrics of the current font, honoring the values defined by the type designer.}
The \autodoc:command{\hrulefill} inserts an infinite horizontal rubber, similar to an \autodoc:command{\hfill}, but—as its name implies—filled with a rule (that is, a solid line).
By default, it stands on the baseline and has a thickness of 0.2pt, below the baseline.
It supports optional parameters \autodoc:parameter{raise=<dimension>} and \autodoc:parameter{thickness=<dimension>} to adjust the position and thickness of the line, respectively.
The former accepts a negative measurement, to lower the line.
Alternatively, use the \autodoc:parameter{position} option, which can be set to \code{underline} or \code{strikethrough}.
In that case, it honors the current font metrics and the line is drawn at the appropriate position and, by default, with the relevant thickness.
You can still set a custom thickness with the \autodoc:parameter{thickness} parameter.
For instance, \autodoc:command{\hrulefill[position=underline]} gives:
\hrulefill[position=underline]
Finally, \autodoc:command{\fullrule} draws a thin standalone rule across the width of a full text line.
Accepted parameters are \autodoc:parameter{raise} and \autodoc:parameter{thickness}, with the same meanings as above.
\end{document}
]]
return package