local icu = require("justenoughicu")
local formatNumber = {
und = {
alpha = function (num)
local out = ""
local a = string.byte("a")
repeat
num = num - 1
out = string.char(num % 26 + a) .. out
num = (num - num % 26) / 26
until num < 1
return out
end,
greek = function (num)
local out = ""
local a = SU.codepoint("α") if num < 18 then
out = luautf8.char(num + a - 1)
elseif num < 25 then
out = luautf8.char(num + a)
else
SU.error("Greek numbering is only supported up to 24")
end
return out
end,
},
}
local icuStyles = {
default = 0, decimal = 1, string = 5, ordinal = 6, }
local icuStyleBypass = {
roman = true,
}
local icuFormat = function (num, lang, options)
if not lang and not options.system then
SU.warn("Number formatting needs a language or a numbering system")
return tonumber(num)
end
options.style = not options.style and "default" or options.style
local icustyle = options.style and icuStyles[options.style]
if not icustyle then
SU.warn("Number formatting style is unrecognized (using default as fallback)")
icustyle = 0
end
local iculocale = lang or ""
if options.system then
options.system = options.system:lower()
iculocale = iculocale .. "@numbers=" .. options.system
if icuStyleBypass[options.system] then
icustyle = 1
end
end
local ok, result = pcall(icu.format_number, num, iculocale, icustyle)
if ok and options.system and icustyle == 0 and options.system ~= "latn" and result == tostring(num) then
ok, result = pcall(icu.format_number, num, "@numbers=" .. options.system, 1)
end
if not ok then
SU.warn("Number formatting failed: " .. tostring(result))
end
return tostring(ok and result or num)
end
setmetatable(formatNumber, {
__call = function (self, num, options, case)
if math.abs(num) > 9223372036854775807 then
SU.warn("Integers larger than 64 bits do not reproduce properly in all formats")
end
options = options or {}
if type(options) ~= "table" then
SU.deprecated(
"Previous syntax of SU.formatNumber",
"new syntax for SU.formatNumber",
"0.14.6",
"0.16.0",
[[
Previous syntax was SU.formatNumber(num, format[, case]) with a format string
New syntax is SU.formatNumber(num, options[, case]) with an options table,
possibly containing:
- system: a numbering system string, e.g. "latn" (= "arabic"), "roman", "arab", etc.
With the addition of "alpha" and "greek".
Casing is taken into account (e.g. roman, Roman, ROMAN) unless specified
- style: a format style string, i.e. "default", "decimal", "ordinal", "string")
E.g. in English and latin script: 1234 1,234 1,124th one thousand...
Possibly extended by additional language-specific formatting rules.
Note that the new syntax doesn't handle casing on the format style, for separation of
concerns.
]]
)
if not case then
if options:match("^%l") then
case = "lower"
elseif options:match("^.%l") then
case = "title"
else
case = "upper"
end
end
if options:lower() == "nth" then
SU.deprecated("Format 'nth' in SU.formatNumber", "'ordinal' in SU.formatNumber", "0.14.6", "0.16.0")
options = { style = "ordinal" }
elseif options:lower() == "string" then
options = { style = "string" }
elseif options:lower() == "ordinal" and SILE.settings:get("document.language") == "tr" then
SU.deprecated(
"Format 'ordinal' in Turkish in SU.formatNumber",
"'ordinal-string' in SU.formatNumber",
"0.14.6",
"0.16.0"
)
options = { style = "ordinal-string" }
else
options = { system = options }
end
end
if options.system == "arabic" then
options.system = "latn"
end
local system = options.system
if not case then
if system then
if system:match("^%l") then
case = "lower"
elseif system:match("^.%l") then
case = "title"
else
case = "upper"
end
else
case = "lower"
end
end
system = system and system:lower()
local lang = system and system == "roman" and "la" or SILE.settings:get("document.language")
local style = options.style
local result
if self[lang] and style and type(self[lang][style]) == "function" then
result = self[lang][style](num, options)
elseif style and type(self["und"][style]) == "function" then
result = self.und[system](num, options)
elseif system and type(self["und"][system]) == "function" then
result = system and self.und[system](num, options)
else
result = icuFormat(num, lang, options)
end
return icu.case(result, lang, case)
end,
})
return formatNumber