local base = require("packages.base")
local package = pl.class(base)
package._name = "ruby"
local isLatin = function (char)
return (char > 0x20 and char <= 0x24F) or (char >= 0x300 and char <= 0x36F)
or (char >= 0x1DC0 and char <= 0x1EFF) or (char >= 0x2C60 and char <= 0x2c7F)
end
local checkIfSpacerNeeded = function (reading)
if not SILE.scratch.lastRubyBox then return end
if not isLatin(SU.codepoint(SU.firstChar(reading))) then return end
local top = #SILE.typesetter.state.nodes
if top < 2 then return end
if SILE.typesetter.state.nodes[top] ~= SILE.scratch.lastRubyBox
and SILE.typesetter.state.nodes[top-1] ~= SILE.scratch.lastRubyBox then
return
end
if not isLatin(SU.codepoint(SU.lastChar(SILE.scratch.lastRubyText))) then return end
SILE.typesetter:pushGlue(SILE.settings:get("ruby.latinspacer"))
end
function package:_init ()
base._init(self)
self:loadPackage("font-fallback")
SILE.call("font:add-fallback", { family = "Noto Sans CJK JP" })
SILE.languageSupport.loadLanguage("ja")
end
function package.declareSettings (_)
SILE.settings:declare({
parameter = "ruby.height",
type = "measurement",
default = SILE.measurement("1zw"),
help = "Vertical offset between the ruby and the main text"
})
SILE.settings:declare({
parameter = "ruby.latinspacer",
type = "glue",
default = SILE.nodefactory.glue("0.25em"),
help = "Glue added between consecutive Latin ruby"
})
end
function package:registerCommands ()
self:registerCommand("ruby:font", function (_, _)
SILE.call("font", { size = "0.6zw", weight = 800 })
end)
self:registerCommand("ruby", function (options, content)
local reading = SU.required(options, "reading", "\\ruby")
SILE.typesetter:setpar("")
checkIfSpacerNeeded(reading)
local rubybox = SILE.call("hbox", {}, function ()
SILE.settings:temporarily(function ()
SILE.call("noindent")
SILE.call("ruby:font")
SILE.typesetter:typeset(reading)
end)
end)
rubybox.outputYourself = function (box, typesetter, line)
local ox = typesetter.frame.state.cursorX
local oy = typesetter.frame.state.cursorY
typesetter.frame:advanceWritingDirection(rubybox.width)
typesetter.frame:advancePageDirection(-SILE.settings:get("ruby.height"))
SILE.outputter:setCursor(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY)
for i = 1, #(box.value) do
local node = box.value[i]
node:outputYourself(typesetter, line)
end
typesetter.frame.state.cursorX = ox
typesetter.frame.state.cursorY = oy
end
local cbox = SILE.call("hbox", {}, content)
SU.debug("ruby", "base box is", cbox)
SU.debug("ruby", "reading is", rubybox)
if cbox:lineContribution() > rubybox:lineContribution() then
SU.debug("ruby", "Base is longer, offsetting ruby to fit")
rubybox.width = SILE.length(cbox:lineContribution() - rubybox:lineContribution())/2
else
local diff = rubybox:lineContribution() - cbox:lineContribution()
local to_insert = SILE.length(diff / 2)
SU.debug("ruby", "Ruby is longer, inserting", to_insert, "either side of base")
cbox.width = rubybox:lineContribution()
rubybox.height = 0
rubybox.width = 0
table.insert(cbox.value, 1, SILE.nodefactory.glue(to_insert))
table.insert(cbox.value, SILE.nodefactory.glue(to_insert))
end
SILE.scratch.lastRubyBox = rubybox
SILE.scratch.lastRubyText = reading
end)
end
package.documentation = [[
\begin{document}
\font:add-fallback[family=Noto Sans CJK JP]
\use[module=packages.ruby]
Japanese texts often contain pronunciation hints (called \em{furigana}) for difficult kanji or foreign words.
These hints are traditionally placed either above (in horizontal typesetting) or beside (in vertical typesetting) the word that they explain.
The typesetting term for these glosses is \em{ruby}.
The \autodoc:package{ruby} package provides the \autodoc:command[check=false]{\ruby[reading=<ruby text>]{<base text>}} command which sets a piece of ruby above or beside the base text.
For example:
\set[parameter=ruby.height,value=12pt]
\define[command=ja]{\font[family=Noto Sans CJK JP,language=ja]{\process}}
\begin{autodoc:codeblock}
\\ruby[reading=\ja{れいわ}]\{\ja{令和}\}
\end{autodoc:codeblock}
Produces:
\medskip
\ja{\ruby[reading=れいわ]{令和}}
\font:remove-fallback
\end{document}
]]
return package