Module:TextFit: Difference between revisions

Add shared text-fitting helper: per-glyph advance widths for Retail Demo and Open Sans, with a scale-invariant fitter for sizing text into a box (with help from vergir-bot LLM)
Vergir (talk | contribs)
add bold option to parse/fitText
Line 1: Line 1:
--[==[
--[==[
Shared text-fitting helper.


Estimates the largest font size at which a string will wrap into a box of a
Estimates the largest font size at which a string will wrap into a box of a
given width and height, using real per-glyph advance widths rather than a
given width and height, using real per-glyph advance widths rather than a
character count. Used by [[Module:Abilities/card]] to size ability upgrade
character count.
descriptions; [[Module:ItemBox]] can migrate onto it.


Widths are in em. Sources, all extracted with fonttools:
Widths are in em. Sources, all extracted with fonttools:
Line 11: Line 9:
   Retail Demo bold    [[:File:Retaildemo-bold.woff2]]    unitsPerEm 1000
   Retail Demo bold    [[:File:Retaildemo-bold.woff2]]    unitsPerEm 1000
   Open Sans            [[:File:Open_Sans.woff2]]          unitsPerEm 2048
   Open Sans            [[:File:Open_Sans.woff2]]          unitsPerEm 2048
Retail Demo is subset to 65 codepoints (space, comma, period, 0-9, A-Z, a-z).
MediaWiki:Common.css declares the card font stack as
'Retail Demo', 'Open Sans', 'PT Serif', serif, so the browser falls back per
character; FALLBACK_WIDTHS holds the Open Sans widths actually used for
punctuation, Cyrillic and accented Latin.
]==]
]==]


Line 99: Line 91:
}
}


-- No bold Open Sans is loaded, so browsers synthesise it, which widens
-- No bold Open Sans is loaded, so browsers synthesise it
-- advances slightly. Retail Demo's own measured bold/regular mean is 1.037.
-- Estimated from Retail Demo's measured bold/regular of 1.037.
local SYNTH_BOLD = 1.03
local SYNTH_BOLD = 1.04


-- Characters in neither face (CJK, rare symbols).
-- Characters in neither face (CJK, rare symbols).
Line 144: Line 136:
-- Words break only on real whitespace: NBSP and tag boundaries do not split
-- Words break only on real whitespace: NBSP and tag boundaries do not split
-- them, matching how the browser wraps.
-- them, matching how the browser wraps.
function p.parse(src)
function p.parse(src, baseBold)
src = mw.text.killMarkers(src or "")
src = mw.text.killMarkers(src or "")
src = src:gsub("%[%[[Ff]ile:[^%[%]]*%]%]", "")
src = src:gsub("%[%[[Ff]ile:[^%[%]]*%]%]", "")
Line 156: Line 148:


local function boldNow()
local function boldNow()
if baseBold then return true end
for k = #stack, 1, -1 do
for k = #stack, 1, -1 do
if stack[k] then return true end
if stack[k] then return true end
Line 221: Line 214:
-- Greedy wrap with the usable width normalised to 1, so `r` is the font size
-- Greedy wrap with the usable width normalised to 1, so `r` is the font size
-- expressed as a fraction of that width. Returns the number of rendered lines.
-- expressed as a fraction of that width. Returns the number of rendered lines.
local function lineCount(lines, r)
local function lineCount(lines, r, bold)
local space = charWidth(" ", false) * r
local space = charWidth(" ", bold == true) * r
local total = 0
local total = 0
for _, words in ipairs(lines) do
for _, words in ipairs(lines) do
Line 246: Line 239:
-- container whose content box is exactly that width; no pixel geometry is
-- container whose content box is exactly that width; no pixel geometry is
-- needed here or in the caller.
-- needed here or in the caller.
--
-- Line counting is scale-invariant in width -- every term scales with the font
-- size -- so the ratio is all this needs. Only the height constraint carries a
-- shape, and that is given as an aspect ratio.
--
--
-- opts:
-- opts:
Line 262: Line 251:
local minPct = opts.min or 1
local minPct = opts.min or 1
local step  = opts.step or 0.1
local step  = opts.step or 0.1
local bold  = opts.bold == true


local lines = p.parse(text)
local lines = p.parse(text, bold)


local widest = 0
local widest = 0
Line 278: Line 268:


-- Then shrink until the wrapped block fits vertically
-- Then shrink until the wrapped block fits vertically
while pct > minPct and lineCount(lines, pct / 100) * lineH * (pct / 100) > aspect do
while pct > minPct and lineCount(lines, pct / 100, bold) * lineH * (pct / 100) > aspect do
pct = pct - step
pct = pct - step
end
end