SILE.settings:declare({ parameter = "shaper.variablespaces", type = "boolean", default = true })
SILE.settings:declare({ parameter = "shaper.spaceenlargementfactor", type = "number or integer", default = 1 })
SILE.settings:declare({ parameter = "shaper.spacestretchfactor", type = "number or integer", default = 1 / 2 })
SILE.settings:declare({ parameter = "shaper.spaceshrinkfactor", type = "number or integer", default = 1 / 3 })
SILE.settings:declare({
parameter = "shaper.tracking",
type = "number or nil",
default = nil,
})
makenodes = function (token, options)
return SILE.shaper:createNnodes(token, SILE.font.loadDefaults(options or {}))
end
local function shapespace (spacewidth)
spacewidth = SU.cast("measurement", spacewidth)
local absoluteSpaceWidth = math.abs(spacewidth:tonumber())
local length = spacewidth * SILE.settings:get("shaper.spaceenlargementfactor")
local stretch = absoluteSpaceWidth * SILE.settings:get("shaper.spacestretchfactor")
local shrink = absoluteSpaceWidth * SILE.settings:get("shaper.spaceshrinkfactor")
return SILE.types.length(length, stretch, shrink)
end
local shaper = pl.class()
shaper.type = "shaper"
shaper._name = "base"
function shaper:measureSpace (options)
local ss = SILE.settings:get("document.spaceskip")
if ss then
SILE.settings:temporarily(function ()
SILE.settings:set("font.size", options.size)
SILE.settings:set("font.family", options.family)
SILE.settings:set("font.filename", options.filename)
ss = ss:absolute()
end)
return ss
end
local items, width = self:shapeToken(" ", options)
if not width and not items[1] then
SU.warn("Could not measure the width of a space")
return SILE.types.length()
end
return shapespace(width and width.length or items[1].width)
end
function shaper:measureChar (char)
local options = SILE.font.loadDefaults({})
options.tracking = SILE.settings:get("shaper.tracking")
local items = self:shapeToken(char, options)
if #items > 0 then
return { height = items[1].height, width = items[1].width }
else
SU.error("Unable to measure character", char)
end
end
function shaper.shapeToken (_, _, _)
SU.error("Abstract function shapeToken called", true)
end
function shaper.getFace (_)
SU.error("Abstract function getFace called", true)
end
function shaper.addShapedGlyphToNnodeValue (_, _, _)
SU.error("Abstract function addShapedGlyphToNnodeValue called", true)
end
function shaper.preAddNodes (_, _, _) end
function shaper:createNnodes (token, options)
options.tracking = SILE.settings:get("shaper.tracking")
local items, _ = self:shapeToken(token, options)
if #items < 1 then
return {}
end
local lang = options.language
SILE.languageSupport.loadLanguage(lang)
local nodeMaker = SILE.nodeMakers[lang] or SILE.nodeMakers.unicode
local nodes = {}
for node in nodeMaker(options):iterator(items, token) do
table.insert(nodes, node)
end
return nodes
end
function shaper:formNnode (contents, token, options)
local nnodeContents = {}
local totalWidth = 0
local totalDepth = 0
local totalHeight = 0
local nnodeValue = { text = token, options = options, glyphString = {} }
SILE.shaper:preAddNodes(contents, nnodeValue)
local misfit = false
if SILE.typesetter.frame and SILE.typesetter.frame:writingDirection() == "TTB" then
if options.direction == "LTR" then
misfit = true
end
else
if options.direction == "TTB" then
misfit = true
end
end
for i = 1, #contents do
local glyph = contents[i]
if (options.direction == "TTB") ~= misfit then
if glyph.width > totalHeight then
totalHeight = glyph.width
end
totalWidth = totalWidth + glyph.height
else
if glyph.depth > totalDepth then
totalDepth = glyph.depth
end
if glyph.height > totalHeight then
totalHeight = glyph.height
end
totalWidth = totalWidth + glyph.width
end
self:addShapedGlyphToNnodeValue(nnodeValue, glyph)
end
table.insert(
nnodeContents,
SILE.types.node.hbox({
depth = totalDepth,
height = totalHeight,
misfit = misfit,
width = SILE.types.length(totalWidth),
value = nnodeValue,
})
)
return SILE.types.node.nnode({
nodes = nnodeContents,
text = token,
misfit = misfit,
options = options,
language = options.language,
})
end
function shaper.makeSpaceNode (_, options, item)
local width
if SILE.settings:get("shaper.variablespaces") then
width = shapespace(item.width)
else
width = SILE.shaper:measureSpace(options)
end
return SILE.types.node.glue(width)
end
function shaper.debugVersions (_) end
return shaper