Module:ItemBoxGive feedback
Documentation for this module may be created at Module:ItemBox/doc
local p = {}
local ustring = mw.ustring
-- ============================================================================
-- Retail Demo (bold) glyph advance widths, in em of the font size.
-- unitsPerEm = 1000. Measured 2026-07-20 with fontTools from
-- File:Retaildemo-bold.woff2 ( /images/d/d5/Retaildemo-bold.woff2 ),
-- the bold face used by .modName.
--
-- THIS TABLE IS FONT-SPECIFIC. If that file is re-uploaded with different
-- metrics, name fitting will drift; regenerate the table from the new file's
-- hmtx / unitsPerEm.
-- ============================================================================
local WIDTH = {
A=0.677, B=0.590, C=0.621, D=0.693, E=0.552, F=0.530, G=0.686, H=0.702,
I=0.276, J=0.301, K=0.637, L=0.491, M=0.904, N=0.695, O=0.741, P=0.585,
Q=0.740, R=0.628, S=0.555, T=0.555, U=0.694, V=0.668, W=0.913, X=0.667,
Y=0.635, Z=0.666,
a=0.573, b=0.608, c=0.473, d=0.604, e=0.570, f=0.382, g=0.558, h=0.608,
i=0.269, j=0.266, k=0.584, l=0.268, m=0.907, n=0.602, o=0.611, p=0.604,
q=0.605, r=0.392, s=0.496, t=0.404, u=0.602, v=0.549, w=0.776, x=0.571,
y=0.550, z=0.561,
["0"]=0.662, ["1"]=0.312, ["2"]=0.560, ["3"]=0.566, ["4"]=0.612,
["5"]=0.562, ["6"]=0.601, ["7"]=0.520, ["8"]=0.583, ["9"]=0.598,
[" "]=0.240,
}
-- Characters absent from Retail Demo (hyphen, apostrophe, ...) are drawn by the
-- browser in the fallback sans-serif; this is a reasonable stand-in width.
local FALLBACK = 0.60
-- Card geometry in px at the reference scale where 1em = 10px. The card is
-- scaled through font-size on .CitadelShopMod (the |size= parameter) and the
-- value returned here is em, so it scales together with everything else.
local USABLE_W = 72 -- 80px card minus 4px padding on each side
local NAME_H = 42 -- vertical room the wrapped name may occupy
local LINE_H = 1.2 -- must match .modName line-height
local MAX_PX = 15 -- full size (1.50em)
local MIN_PX = 8 -- hard floor (0.80em)
local function charWidth(ch)
local w = WIDTH[ch]
if w then return w end
local cp = ustring.codepoint(ch) or 0
-- CJK / full-width ranges render roughly twice as wide as Latin
if (cp >= 0x1100 and cp <= 0x115F) or (cp >= 0x2E80 and cp <= 0xA4CF)
or (cp >= 0xAC00 and cp <= 0xD7A3) or (cp >= 0xF900 and cp <= 0xFAFF)
or (cp >= 0xFF00 and cp <= 0xFF60) or (cp >= 0xFFE0 and cp <= 0xFFE6) then
return 2.0
end
return FALLBACK
end
local function wordWidth(word)
local total = 0
for ch in ustring.gmatch(word, ".") do
total = total + charWidth(ch)
end
return total
end
-- Greedy line count when filling USABLE_W at font size `px`. Close enough to
-- text-wrap:balance for sizing, since both keep every word intact.
local function lineCount(words, px)
local space = charWidth(" ") * px
local cur, lines = 0, 1
for _, w in ipairs(words) do
local ww = wordWidth(w) * px
if cur == 0 then
cur = ww
elseif cur + space + ww <= USABLE_W then
cur = cur + space + ww
else
lines = lines + 1
cur = ww
end
end
return lines
end
-- Returns the name font size as an em string (scales with the card).
function p.getFontSize(frame)
local name = ustring.gsub(frame.args[1] or "", "^%s*(.-)%s*$", "%1")
if name == "" then return string.format("%.2f", MAX_PX / 10) end
local words = {}
for w in ustring.gmatch(name, "%S+") do words[#words + 1] = w end
local widest = 0
for _, w in ipairs(words) do
local ww = wordWidth(w)
if ww > widest then widest = ww end
end
local px = USABLE_W / widest
if px > MAX_PX then px = MAX_PX end
while px > MIN_PX and lineCount(words, px) * LINE_H * px > NAME_H do
px = px - 0.25
end
if px < MIN_PX then px = MIN_PX end
local snapped = math.floor(px * 2 + 0.5) / 2
if lineCount(words, snapped) * LINE_H * snapped > NAME_H then
snapped = math.floor(px * 2) / 2
end
return string.format("%.2f", snapped / 10)
end
return p