Module:TextFit: Difference between revisionsGive feedback
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) |
add bold option to parse/fitText |
||
| Line 1: | Line 1: | ||
--[==[ | --[==[ | ||
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 | character count. | ||
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 | ||
]==] | ]==] | ||
| Line 99: | Line 91: | ||
} | } | ||
-- No bold Open Sans is loaded, so browsers synthesise it | -- No bold Open Sans is loaded, so browsers synthesise it | ||
-- | -- Estimated from Retail Demo's measured bold/regular of 1.037. | ||
local SYNTH_BOLD = 1. | 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(" ", | 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. | ||
-- | -- | ||
-- 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 | ||