Module:ItemBox: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Vergir (talk | contribs)
m comments update
Vergir (talk | contribs)
Emit the name size in cqw against the name area, dropping the em conversion and grid snapping (with help from vergir-bot LLM)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
local p = {}
local p = {}
local ustring = mw.ustring
local ustring = mw.ustring
local textfit = require "Module:TextFit"


-- ============================================================================
-- ============================================================================
--  Retail Demo (bold) glyph advance widths, in em of the font size.
--  Name box geometry, in em of .itembox's font-size (1em = 10px at size=1).
--  unitsPerEm = 1000. Measured from /images/d/d5/Retaildemo-bold.woff2
--  Mirrors Template:ItemBox/styles.css:
--   Table can be regenerated with fontforge / fonttools
--    .itembox            width: 8em
--    .itembox-name-area  height: 4.5em; padding: 0.5em 0.4em (border-box)
--    .itembox-name      line-height: 1.2; font-size: 1.5em (overridden inline)
--  so the name's content box is 8 - 2*0.4 = 7.2em wide and 4.5 - 2*0.5 = 3.5em
--  tall. Only the ratio and the size limits are read from those figures: the
-- size is emitted in cqw against .itembox-name-area, so the browser supplies
-- the actual width. Glyph widths live in [[Module:TextFit]].
-- ============================================================================
-- ============================================================================
local WIDTH = {
local NAME_W = 7.2
    A=0.677, B=0.590, C=0.621, D=0.693, E=0.552, F=0.530, G=0.686, H=0.702,
local NAME_H = 3.5
    I=0.276, J=0.301, K=0.637, L=0.491, M=0.904, N=0.695, O=0.741, P=0.585,
local LINE_H = 1.2
    Q=0.740, R=0.628, S=0.555, T=0.555, U=0.694, V=0.668, W=0.913, X=0.667,
local MAX_EM = 1.5
    Y=0.635, Z=0.666,
local MIN_EM = 0.8
    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
-- Returns the name font size as a cqw string, measured against
-- browser in the fallback sans-serif; this is a reasonable stand-in width.
-- .itembox-name-area, which [[Template:ItemBox]] makes a query container.
local FALLBACK = 0.60
 
-- Card geometry in px at the reference scale where 1em = 10px. The card is
-- scaled through font-size on .itembox (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 .itembox-name 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)
function p.getFontSize(frame)
     local name = ustring.gsub(frame.args[1] or "", "^%s*(.-)%s*$", "%1")
     local name = ustring.gsub(frame.args[1] or "", "^%s*(.-)%s*$", "%1")
     if name == "" then return string.format("%.2f", MAX_PX / 10) end
    local max = MAX_EM / NAME_W * 100          -- 20.83cqw
 
     if name == "" then return string.format("%.2fcqw", max) 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)
     return string.format("%.2fcqw", textfit.fit(name, {
        aspect    = NAME_H / NAME_W,
        lineHeight = LINE_H,
        max        = max,
        min        = MIN_EM / NAME_W * 100,    -- 11.11cqw
    }))
end
end



Latest revision as of 19:14, 1 August 2026

Documentation for this module may be created at Module:ItemBox/doc

local p = {}
local ustring = mw.ustring
local textfit = require "Module:TextFit"

-- ============================================================================
--  Name box geometry, in em of .itembox's font-size (1em = 10px at size=1).
--  Mirrors Template:ItemBox/styles.css:
--    .itembox            width: 8em
--    .itembox-name-area  height: 4.5em; padding: 0.5em 0.4em (border-box)
--    .itembox-name       line-height: 1.2; font-size: 1.5em (overridden inline)
--  so the name's content box is 8 - 2*0.4 = 7.2em wide and 4.5 - 2*0.5 = 3.5em
--  tall. Only the ratio and the size limits are read from those figures: the
--  size is emitted in cqw against .itembox-name-area, so the browser supplies
--  the actual width. Glyph widths live in [[Module:TextFit]].
-- ============================================================================
local NAME_W = 7.2
local NAME_H = 3.5
local LINE_H = 1.2
local MAX_EM = 1.5
local MIN_EM = 0.8

-- Returns the name font size as a cqw string, measured against
-- .itembox-name-area, which [[Template:ItemBox]] makes a query container.
function p.getFontSize(frame)
    local name = ustring.gsub(frame.args[1] or "", "^%s*(.-)%s*$", "%1")
    local max = MAX_EM / NAME_W * 100          -- 20.83cqw
    if name == "" then return string.format("%.2fcqw", max) end

    return string.format("%.2fcqw", textfit.fit(name, {
        aspect     = NAME_H / NAME_W,
        lineHeight = LINE_H,
        max        = max,
        min        = MIN_EM / NAME_W * 100,    -- 11.11cqw
    }))
end

-- User for stable per-item texture variety
local function hash(s)
    local h = 5381
    for i = 1, #s do
        h = (h * 33 + s:byte(i)) % 2147483647
    end
    return h
end

function p.variants(frame)
    local h = hash(frame.args[1] or "")
    local icon = (h % 3) + 1
    local wear = (math.floor(h / 3) % 4) + 1
    local tex  = (math.floor(h / 12) % 3) + 1
    return string.format("itembox-icon-v%d itembox-wear-v%d itembox-texture-v%d",
        icon, wear, tex)
end

return p