Module:font list
Appearance
- This module lacks a documentation subpage. Please create it.
- Useful links: subpage list • links • transclusions • testcases • sandbox
local export = {}
local SAMPLE_TEXTS = mw.loadData("Module:font list/data").sample_texts
-- here is a CSS parser, until it is rewritten
local function css_state_new(text)
return {
source = text,
source_index = 1,
token_class = nil,
token_data = nil
}
end
local CSS_TOKEN_WHITESPACE = nil -- note: assumed to be nil by e.g. css_token_logical_next
local CSS_TOKEN_END_OF_FILE = -1
local CSS_TOKEN_IDENTIFIER = 0
local CSS_TOKEN_STRING = 1
local CSS_TOKEN_NUMBER = 2
local CSS_TOKEN_AT = 3
local CSS_TOKEN_SEMICOLON = 100 -- ;
local CSS_TOKEN_BRACE_LEFT = 101 -- {
local CSS_TOKEN_BRACE_RIGHT = 102 -- }
local CSS_TOKEN_COLON = 103 -- :
local CSS_TOKEN_DOT = 104 -- .
local CSS_TOKEN_HASH = 105 -- #
local CSS_TOKEN_PLUS = 106 -- +
local CSS_TOKEN_GREATER = 107 -- >
local CSS_TOKEN_COMMA = 108 -- ,
local CSS_TOKEN_STAR = 109 -- *
local CSS_TOKEN_PERCENT = 110 -- %
local CSS_TOKEN_TILDE = 111 -- ~
local CSS_TOKEN_BRACKET_LEFT = 112 -- [
local CSS_TOKEN_BRACKET_RIGHT = 113 -- ]
local CSS_TOKEN_PAREN_LEFT = 114 -- (
local CSS_TOKEN_PAREN_RIGHT = 115 -- )
local CSS_TOKEN_PIPE = 116 -- |
local CSS_TOKEN_EQUALS = 117 -- =
local CSS_TOKEN_INCLUDES = 118 -- ~=
local CSS_TOKEN_BAR_EQUALS = 119 -- |=
local CSS_TOKEN_PREFIX_HAS = 120 -- ^=
local CSS_TOKEN_SUFFIX_HAS = 121 -- $=
local CSS_TOKEN_TEXT_INCLUDES = 122 -- *=
local CSS_TOKEN_IMPORTANT = 123 -- !important
local CSS_TOKEN_MINUS = 124 -- -
local CSS_TOKEN_DIVIDES = 125 -- /
local CSS_TOKEN_LOOKUP = {
[";"] = CSS_TOKEN_SEMICOLON,
["{"] = CSS_TOKEN_BRACE_LEFT,
["}"] = CSS_TOKEN_BRACE_RIGHT,
[":"] = CSS_TOKEN_COLON,
["."] = CSS_TOKEN_DOT,
["#"] = CSS_TOKEN_HASH,
["+"] = CSS_TOKEN_PLUS,
[">"] = CSS_TOKEN_GREATER,
[","] = CSS_TOKEN_COMMA,
["*"] = CSS_TOKEN_STAR,
["%"] = CSS_TOKEN_PERCENT,
["~"] = CSS_TOKEN_TILDE,
["["] = CSS_TOKEN_BRACKET_LEFT,
["]"] = CSS_TOKEN_BRACKET_RIGHT,
["("] = CSS_TOKEN_PAREN_LEFT,
[")"] = CSS_TOKEN_PAREN_RIGHT,
["|"] = CSS_TOKEN_PIPE,
["="] = CSS_TOKEN_EQUALS,
["/"] = CSS_TOKEN_DIVIDES,
}
local CSS_TOKEN_LOOKUP_EQUALS = {
["~"] = CSS_TOKEN_INCLUDES,
["|"] = CSS_TOKEN_BAR_EQUALS,
["^"] = CSS_TOKEN_PREFIX_HAS,
["$"] = CSS_TOKEN_SUFFIX_HAS,
["*"] = CSS_TOKEN_TEXT_INCLUDES,
}
local function css_token_parse_backslash(src, idx)
local result
if src:match("^[\r\n]", idx) then
local _, end_idx = src:find("^\r?\n?", idx)
idx = end_idx + 1
result = ""
elseif src:match("^['\"]", idx) then
result = src:sub(idx, idx)
idx = idx + 1
elseif src:match("^[0-9A-Fa-f]", idx) then
local _, end_idx = src:find("^[0-9A-Fa-f]+", idx)
if end_idx > idx + 5 then
-- at most 6 hex digits
end_idx = idx + 5
end
result = mw.ustring.char(tonumber(src:sub(idx, end_idx), 16))
idx = end_idx + 1
if src:match("^%s", idx) then
idx = idx + 1
end
else
result = src:sub(idx, idx)
idx = idx + 1
end
return idx, result
end
local function css_token_read_next(state)
local src = state.source
local idx = state.source_index
if idx > #src then
-- nothing more
return CSS_TOKEN_END_OF_FILE
end
while src:match("^/%*", idx) do
-- skip comments
idx = idx + 2
local _, end_idx = src:find("%*/", idx)
if end_idx == nil then
state.source_index = #src + 1
-- nothing more
return CSS_TOKEN_END_OF_FILE
end
idx = end_idx + 2
end
local c1 = src:sub(idx, idx)
local eq1 = CSS_TOKEN_LOOKUP[c1]
if eq1 then
if src:sub(idx + 1, idx + 1) == "=" then
state.source_index = idx + 2
local eq2 = CSS_TOKEN_LOOKUP_EQUALS[c1]
if eq2 then return eq2 end
end
state.source_index = idx + 1
return eq1
elseif src:match("^%s", idx) then
-- whitespace
local _, end_idx = src:find("^%s+", idx)
state.source_index = end_idx + 1
return CSS_TOKEN_WHITESPACE
elseif src:match("^['\"]", idx) then
-- strings
local quote = src:sub(idx, idx)
idx = idx + 1
local mask = "([" .. quote .. "\\])"
local css_string = ""
while true do
local next_idx, _, next_chr = src:find(mask, idx)
if next_idx == nil then
state.source_index = #src + 1
error("CSS error: unterminated string")
end
css_string = css_string .. src:sub(idx, next_idx - 1)
idx = next_idx + 1
if next_chr == quote then
break
elseif next_chr == "\\" then
-- backslash escape
local result
idx, result = css_token_parse_backslash(src, idx)
css_string = css_string .. result
end
end
state.source_index = idx
return CSS_TOKEN_STRING, css_string
elseif src:match("^[0-9]", idx) or src:match("^%-[0-9.]", idx) or src:match("^%.[0-9]", idx) or src:match("^%+[0-9]", idx) then
-- number
local start_idx = idx
if src:match("^[+-]", idx) then
idx = idx + 1
end
local _, end_idx = src:find("^[0-9]*", idx)
idx = end_idx + 1
-- dot
if src:match("^%.", idx) then
idx = idx + 1
local _, end_idx = src:find("^[0-9]*", idx)
idx = end_idx + 1
end
local result = tonumber(src:sub(start_idx, idx - 1))
state.source_index = idx
return CSS_TOKEN_NUMBER, result
elseif src:match("^[a-zA-Z]", idx) or src:match("^%-?%-?[a-zA-Z]", idx) or src:match("^%-%-%-", idx) then
-- identifier
local start_idx = idx
local identifier = ""
while true do
local next_idx, _, next_chr = src:find("[^A-Za-z0-9\128-\255_-]", idx)
if next_idx == nil then
next_idx = #src + 1
end
identifier = identifier .. src:sub(idx, next_idx - 1)
idx = next_idx
if next_chr == "\\" then
-- backslash escape
local result
idx, result = css_token_parse_backslash(src, idx + 1)
identifier = identifier .. result
elseif next_chr ~= "\\" then
break
end
end
state.source_index = idx
if identifier == "-" then
return CSS_TOKEN_MINUS
end
return CSS_TOKEN_IDENTIFIER, identifier
elseif src:match("^@[A-Za-z-]", idx) then
-- @ token
local start_idx = idx + 1
local _, end_idx = src:find("^[A-Za-z0-9-]*", idx)
state.source_index = end_idx + 1
return CSS_TOKEN_AT, src.sub(start_idx, end_idx)
elseif src:match("^!important", idx) then
state.source_index = idx + 10
return CSS_TOKEN_IMPORTANT
end
-- unrecognized character.
error("CSS error: unrecognized character '" .. src:sub(idx, idx) .. "'")
end
local function css_token_next(state)
local token, token_data = css_token_read_next(state)
state.token_class, state.token_data = token, token_data
return token, token_data
end
local function css_token_logical_next(state)
-- css_token_next but skips whitespace
local token, token_data = css_token_read_next(state)
while not token do
token, token_data = css_token_read_next(state)
end
state.token_class, state.token_data = token, token_data
return token, token_data
end
local CSS_COLON_SUBSELECTOR = {
["current"] = true,
["has"] = true,
["is"] = true,
["matches"] = true,
["not"] = true,
["where"] = true,
}
local CSS_COLON_TEXT_NUMBER = {
["dir"] = true,
["lang"] = true,
["nth-child"] = true,
["nth-of-type"] = true,
["nth-last-child"] = true,
["nth-last-of-type"] = true,
}
local function css_parse_selectors(state, token, token_data)
local selectors = {}
local leaf = {}
table.insert(selectors, leaf)
while true do
if token == CSS_TOKEN_STAR then
-- match all
token, token_data = css_token_next(state)
elseif token == CSS_TOKEN_IDENTIFIER then
-- element
if leaf.element then
error("CSS internal error: invalid type selector")
end
leaf.element = token_data
token, token_data = css_token_next(state)
elseif token == CSS_TOKEN_DOT then
-- .class
token, token_data = css_token_next(state)
if token ~= CSS_TOKEN_IDENTIFIER then
error("CSS error: invalid class selector")
end
if leaf.class then
if type(leaf.class) == "string" then
leaf.class = { leaf.class }
end
table.insert(leaf.class, token_data)
else
leaf.class = token_data
end
token, token_data = css_token_next(state)
elseif token == CSS_TOKEN_HASH then
-- #id
token, token_data = css_token_next(state)
if token ~= CSS_TOKEN_IDENTIFIER then
error("CSS error: invalid class selector")
end
if leaf.id then
leaf.id = { } -- never matches
else
leaf.id = token_data
end
token, token_data = css_token_next(state)
elseif token == CSS_TOKEN_COLON then
-- :pseudo...(...)
token, token_data = css_token_next(state)
if token ~= CSS_TOKEN_IDENTIFIER then
error("CSS error: invalid colon selector")
end
local colon_type = mw.ustring.lower(token_data)
local colon = { type = colon_type }
if not leaf.pseudo then
leaf.pseudo = {}
end
table.insert(leaf.pseudo, colon)
token, token_data = css_token_next(state)
if token == CSS_TOKEN_PAREN_LEFT then
-- (...)
token, token_data = css_token_logical_next(state)
if CSS_COLON_SUBSELECTOR[colon_type] then
local selectors
token, token_data, selectors = css_parse_selectors(state, token, token_data)
colon.selectors = selectors
elseif CSS_COLON_TEXT_NUMBER[colon_type] then
local content = ""
while true do
if token == CSS_TOKEN_NUMBER then
content = content .. tostring(token_data)
elseif token == CSS_TOKEN_IDENTIFIER then
content = content .. token_data
elseif token == CSS_TOKEN_PLUS then
content = content .. "+"
elseif token == CSS_TOKEN_WHITESPACE then
content = content .. " "
elseif token == CSS_TOKEN_PAREN_RIGHT then
token, token_data = css_token_next(state)
break
else
error("CSS error: unsupported token in parameterized pseudo-class")
end
token, token_data = css_token_next(state)
end
colon.value = content
else
error("CSS error: unsupported parameterized pseudo-class")
end
elseif CSS_COLON_SUBSELECTOR[colon_type] or CSS_COLON_TEXT_NUMBER[colon_type] then
error("CSS error: expected parameterized pseudo-class")
end
elseif token == CSS_TOKEN_BRACKET_LEFT then
-- attribute
token, token_data = css_token_logical_next(state)
if token ~= CSS_TOKEN_IDENTIFIER then
error("CSS error: invalid colon selector")
end
local attribute = { name = mw.ustring.lower(token_data) }
if not leaf.attributes then
leaf.attributes = {}
end
table.insert(leaf.attributes, attribute)
local operator
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_EQUALS then
operator = "is"
elseif token == CSS_TOKEN_INCLUDES then
operator = "word"
elseif token == CSS_TOKEN_BAR_EQUALS then
operator = "bar_prefix"
elseif token == CSS_TOKEN_PREFIX_HAS then
operator = "prefix"
elseif token == CSS_TOKEN_SUFFIX_HAS then
operator = "suffix"
elseif token == CSS_TOKEN_TEXT_INCLUDES then
operator = "factor"
else
error("CSS error: unsupported attribute selector operator")
end
local attribute_value
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_STRING then
attribute_value = token_data
elseif token == CSS_TOKEN_IDENTIFIER then
attribute_value = token_data
else
error("CSS error: unsupported attribute selector value")
end
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_IDENTIFIER then
local modifier = mw.ustring.lower(token_data)
if modifier == "s" then
attribute.case_insensitive = false
elseif modifier == "i" then
attribute.case_insensitive = true
else
error("CSS error: unsupported attribute selector modifier")
end
token, token_data = css_token_logical_next(state)
end
if token ~= CSS_TOKEN_BRACE_RIGHT then
error("CSS error: expected ] to end attribute selector")
end
attribute[operator] = attribute_value
token, token_data = css_token_next(state)
else
error("CSS internal error: invalid selector")
end
local had_whitespace = false
had_whitespace = token == CSS_TOKEN_WHITESPACE
if had_whitespace then
token, token_data = css_token_logical_next(state)
end
if token == CSS_TOKEN_COMMA then
-- next selector
leaf = {}
table.insert(selectors, leaf)
token, token_data = css_token_logical_next(state)
elseif token == CSS_TOKEN_BRACE_LEFT or token == CSS_TOKEN_PAREN_RIGHT then
break
elseif token == CSS_TOKEN_PIPE then
-- a|b: namespace combinator
for key in pairs(leaf) do
if key ~= "element" then
error("CSS error: invalid namespace selector")
end
end
if key.namespace or not key.element then
error("CSS error: invalid namespace selector")
end
key.namespace = key.element
key.element = nil
token, token_data = css_token_logical_next(state)
elseif token == CSS_TOKEN_PLUS then
-- a + b: next sibling combinator
leaf.next = {}
leaf = leaf.next
token, token_data = css_token_logical_next(state)
elseif token == CSS_TOKEN_TILDE then
-- a ~ b: subsequent sibling combinator
leaf.after = {}
leaf = leaf.after
token, token_data = css_token_logical_next(state)
elseif token == CSS_TOKEN_GREATER then
-- a > b: child combinator
leaf.child = {}
leaf = leaf.child
token, token_data = css_token_logical_next(state)
elseif had_whitespace then
-- a b: descendant combinator
leaf.descendant = {}
leaf = leaf.descendant
end
end
return token, token_data, selectors
end
local function css_parse_if_end_of_declaration(token)
return token == CSS_TOKEN_SEMICOLON or token == CSS_TOKEN_BRACE_RIGHT or token == CSS_TOKEN_IMPORTANT
end
local CSS_FONT_FAMILY_BUILTIN = {
["serif"] = true,
["sans-serif"] = true,
["monospace"] = true,
["cursive"] = true,
["fantasy"] = true,
["system-ui"] = true,
["ui-serif"] = true,
["ui-sans-serif"] = true,
["ui-monospace"] = true,
["ui-rounded"] = true,
["emoji"] = true,
["math"] = true,
["fangsong"] = true,
["inherit"] = true,
["initial"] = true,
["revert"] = true,
["revert-layer"] = true,
["unset"] = true
}
local CSS_ONE_IDENTIFIER_PROPERTIES = {
['writing-mode'] = true,
['layout-flow'] = true,
}
local function css_parse_declaration_value(state, token, token_data, property)
local result
local important
-- only parse what we need
if property == "font-family" then
local quoted = false
local fonts = {}
local font_name = nil
while true do
if token == CSS_TOKEN_IDENTIFIER then
if font_name then
font_name = font_name .. " " .. token_data
else
font_name = token_data
end
elseif token == CSS_TOKEN_STRING then
quoted = true
if font_name then
font_name = font_name .. " " .. token_data
else
font_name = token_data
end
elseif token == CSS_TOKEN_COMMA then
font_name = font_name or ""
if not quoted and CSS_FONT_FAMILY_BUILTIN[font_name] then
table.insert(fonts, { builtin = font_name })
else
table.insert(fonts, font_name)
end
quoted = false
font_name = nil
elseif not token then
-- skip
elseif css_parse_if_end_of_declaration(token) then
break
else
error("Unexpected token in font-family")
end
token, token_data = css_token_logical_next(state)
end
font_name = font_name or ""
if not quoted and CSS_FONT_FAMILY_BUILTIN[font_name] then
table.insert(fonts, { builtin = font_name })
else
table.insert(fonts, font_name)
end
result = fonts
elseif property == "font-size" then
if token == CSS_TOKEN_NUMBER then
result = tostring(token_data)
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_IDENTIFIER then
result = result .. token_data
token, token_data = css_token_logical_next(state)
elseif token == CSS_TOKEN_PERCENT then
result = result .. "%"
token, token_data = css_token_logical_next(state)
end
elseif token == CSS_TOKEN_IDENTIFIER then
result = token_data
token, token_data = css_token_logical_next(state)
else
error("unsupported font-weight")
end
elseif property == "font-weight" then
if token == CSS_TOKEN_NUMBER then
result = tostring(token_data)
elseif token == CSS_TOKEN_IDENTIFIER then
result = token_data
else
error("unsupported font-weight")
end
token, token_data = css_token_logical_next(state)
elseif CSS_ONE_IDENTIFIER_PROPERTIES[property] then
if token == CSS_TOKEN_IDENTIFIER then
result = token_data
else
error("unsupported " .. property)
end
token, token_data = css_token_logical_next(state)
else
while not css_parse_if_end_of_declaration(token) do
token, token_data = css_token_logical_next(state)
end
result = nil
end
if not css_parse_if_end_of_declaration(token) then
error("CSS error: expected end of declaration")
end
if token ~= CSS_TOKEN_BRACE_RIGHT then
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_IMPORTANT then
important = true
token, token_data = css_token_logical_next(state)
if not (token == CSS_TOKEN_SEMICOLON or token == CSS_TOKEN_BRACE_RIGHT) then
error("CSS error: expected end of declaration")
end
end
end
return token, token_data, result, important
end
local function css_parse_declaration(state, token, token_data)
if token ~= CSS_TOKEN_IDENTIFIER then
error("expected CSS property")
end
local property = mw.ustring.lower(token_data)
token, token_data = css_token_logical_next(state)
if token ~= CSS_TOKEN_COLON then
error("expected colon after CSS property")
end
local value
token, token_data = css_token_logical_next(state)
token, token_data, value, important = css_parse_declaration_value(state, token, token_data, property)
return token, token_data, { property = property, value = value, important = important }
end
local function css_parse_ruleblock(state, token, token_data)
if token ~= CSS_TOKEN_BRACE_LEFT then
error("CSS: expected {")
end
token, token_data = css_token_logical_next(state)
local rules = { }
local rule
while true do
if token == CSS_TOKEN_SEMICOLON or token == CSS_TOKEN_WHITESPACE then
-- next token
token, token_data = css_token_logical_next(state)
elseif token == CSS_TOKEN_BRACE_RIGHT then
token, token_data = css_token_logical_next(state)
break
else
token, token_data, rule = css_parse_declaration(state, token, token_data)
table.insert(rules, rule)
end
end
return token, token_data, rules
end
local function css_parse_atrule(state, token, token_data, rule)
-- skip rest of the statement, or a block of braces
if rule == "container" or rule == "media" or rule == "supports" then
while token ~= CSS_TOKEN_BRACE_LEFT do
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_END_OF_FILE then
error("CSS error: unexpected end of file")
end
end
while token ~= CSS_TOKEN_BRACE_RIGHT do
token, token_data = css_token_logical_next(state)
if token == CSS_TOKEN_END_OF_FILE then
error("CSS error: unexpected end of file")
end
token, token_data = css_parse_ruleset(state, token, token_data)
end
else
while true do
if token == CSS_TOKEN_SEMICOLON then
token, token_data = css_token_logical_next(state)
break
elseif token == CSS_TOKEN_BRACE_LEFT then
token, token_data = css_parse_ruleblock(state, token, token_data)
break
end
end
end
return token, token_data, { at_rule = rule }
end
local function css_parse_ruleset_or_atrule(state, token, token_data)
if token == CSS_TOKEN_END_OF_FILE then
return nil
elseif token == CSS_TOKEN_AT then
return css_parse_atrule(state, token, token_data)
end
local selector, rules
token, token_data, selectors = css_parse_selectors(state, token, token_data)
token, token_data, rules = css_parse_ruleblock(state, token, token_data)
return token, token_data, { selectors = selectors, rules = rules }
end
local function css_parse_document(state)
local rulesets = {}
local ruleset
local token, token_data = css_token_logical_next(state)
while true do
token, token_data, ruleset = css_parse_ruleset_or_atrule(state, token, token_data)
if not ruleset then
break
end
table.insert(rulesets, ruleset)
end
return rulesets
end
-- use CSS data to get font families and sizes
local function script_language_next(state, index)
local selectors = state.selectors
while true do
index = index + 1
local selector = selectors[index]
if not selector then
break
end
if selector.class and type(selector.class) == "string" then
local script_code = selector.class
if state.script_codes[script_code] then
-- is valid script code
-- check if has language
if selector.pseudo then
for _, pseudo in ipairs(selector.pseudo) do
if pseudo.type == "lang" then
local language_code = pseudo.value
if state.language_codes[language_code] then
return index, script_code, language_code, script_code
end
end
end
end
if selector.attributes then
for _, attribute in ipairs(selector.attributes) do
if attribute.name == "lang" and attribute.is then
local language_code = attribute.is
if state.language_codes[language_code] then
return index, script_code, language_code, script_code
end
end
end
end
-- hack to deal with hyphenated codes
if script_code:find("%-") then
local split_lang, split_sc = script_code:match("([a-z-]+)%-([A-Za-z]+)")
if state.script_codes[split_sc] and state.language_codes[split_lang] then
return index, script_code, split_lang, split_sc
end
return index, script_code, nil, split_sc
end
return index, script_code, nil, script_code
end
end
end
end
local function script_language_pairs(selectors, script_codes, language_codes)
return script_language_next, {
selectors = selectors,
script_codes = script_codes,
language_codes = language_codes,
}, 0
end
local function get_font_by_script_language(parsed)
local script_codes = mw.loadData("Module:scripts/code to canonical name")
local language_codes = mw.loadData("Module:languages/code to canonical name")
local keys = {}
local sorted_keys = {}
function make_key(sc, lang)
if lang then
return sc .. "/" .. lang
else
return sc
end
end
for ruleset_i, ruleset in ipairs(parsed) do
if ruleset.selectors and ruleset.rules then
local fonts = {}
local special = {}
for rule_i, rule in ipairs(ruleset.rules) do
if rule.property == "font-family" then
fonts = rule.value
elseif false and rule.value then
special[rule.property] = rule.value
end
end
if #fonts > 1 or (#fonts == 1 and not fonts[1].builtin) then
for pair_i, sc, lang, effective_sc in script_language_pairs(ruleset.selectors, script_codes, language_codes) do
local key = make_key(sc, lang)
local row = keys[key]
if not row then
row = { sc, lang, fonts = fonts }
row.name = sc and script_codes[sc] or nil
row.language_name = lang and language_codes[lang] or nil
keys[key] = row
table.insert(sorted_keys, key)
end
row.fonts = fonts
if false then
for special_key, special_value in pairs(special) do
if not row.css then
row.css = {}
end
row.css[special_key] = special_value
end
end
row.effective_script_code = effective_sc or sc
row.effective_script_name = script_codes[row.effective_script_code]
end
end
end
end
table.sort(sorted_keys, function (ka, kb)
local a = keys[ka]
local b = keys[kb]
return a.effective_script_name .. "/" .. a[1] .. "/" .. (a.language_name or "") < b.effective_script_name .. "/" .. b[1] .. "/" .. (b.language_name or "")
end)
local sorted_rows = {}
for _, key in ipairs(sorted_keys) do
table.insert(sorted_rows, keys[key])
end
return sorted_rows
end
local function make_table(data)
local tokens = {}
local script_row_spans = {}
local scripts_with_lang = {}
for i, row in ipairs(data) do
script_row_spans[row[1]] = (script_row_spans[row[1]] or 0) + #row.fonts + 1
if row[2] then
scripts_with_lang[row.effective_script_code] = true
end
end
local seen_scripts = {}
local default_example_text = SAMPLE_TEXTS[""]
local last_script_heading
for i, row in ipairs(data) do
local sc, lang = unpack(row)
local span
local esc = row.effective_script_code
local script_heading = row.effective_script_name
if last_script_heading ~= script_heading then
if last_script_heading then
table.insert(tokens, "\n|}\n")
end
table.insert(tokens, "==" .. script_heading .. "==\n")
if scripts_with_lang[esc] then
table.insert(tokens, '{| class="wikitable font-list fonts-' .. esc .. [=["
! Code
! Language
! Font
! Sample
]=])
else
table.insert(tokens, '{| class="wikitable font-list fonts-' .. esc .. [=["
! Code
! Font
! Sample
]=])
end
last_script_heading = script_heading
end
table.insert(tokens, "|-\n")
local example_key
if lang then
example_key = sc .. ":" .. lang
else
example_key = sc
end
local example_text
if row.effective_script_code ~= sc then
local example_key2
if lang then
example_key2 = esc .. ":" .. lang
else
example_key2 = esc
end
example_text = SAMPLE_TEXTS[sc] or SAMPLE_TEXTS[example_key2] or SAMPLE_TEXTS[esc] or default_example_text
else
example_text = SAMPLE_TEXTS[example_key] or SAMPLE_TEXTS[sc] or default_example_text
end
if not seen_scripts[sc] then
local name_paren
table.insert(tokens, '|class="codes" rowspan="' .. script_row_spans[sc] .. '"|')
if row.name ~= script_heading then
name_paren = "<br>(" .. row.name .. ")"
else
name_paren = ""
end
table.insert(tokens, '<code>' .. sc .. '</code>' .. name_paren .. '\n')
seen_scripts[sc] = true
end
local col_start = '|class="codes" rowspan="' .. (#row.fonts + 1) .. '"| '
if lang then
table.insert(tokens, col_start .. row.language_name .. '<br>(<code>' .. lang .. '</code>)\n')
elseif scripts_with_lang[sc] then
table.insert(tokens, col_start .. "(other)\n")
elseif scripts_with_lang[esc] then
table.insert(tokens, col_start .. "(all)\n")
end
table.insert(tokens, '|class="fontauto"| <current>\n|')
table.insert(tokens, tostring(mw.html.create("span"):addClass(sc):attr("lang", lang):wikitext(example_text)))
table.insert(tokens, '\n')
for font_i, font in ipairs(row.fonts) do
local css_text, font_name, font_name_class
if font.builtin then
css_text = 'font-family:' .. font.builtin
font_name = "<" .. font.builtin .. ">"
font_name_class = 'fontauto'
else
css_text = font
if mw.ustring.find(css_text, '\\') then
css_text = mw.ustring.gsub(css_text, '\\', '\\\\')
end
if mw.ustring.find(css_text, '"') then
css_text = mw.ustring.gsub(css_text, '"', '\"')
end
css_text = 'font-family:"' .. css_text .. '"'
font_name = font
font_name_class = 'fontname'
end
table.insert(tokens, '|-\n|class="' .. (font_name_class or '') .. '"| ' .. font_name .. '||')
table.insert(tokens, tostring(mw.html.create("span"):attr("lang", lang):addClass(sc):cssText(css_text):wikitext(example_text)))
table.insert(tokens, '\n')
end
end
table.insert(tokens, "\n|}")
return table.concat(tokens, "")
end
function export.show(frame)
local css = mw.title.new("MediaWiki:Gadget-LanguagesAndScripts.css"):getContent()
local state = css_state_new(css)
local parsed = css_parse_document(state)
local data = get_font_by_script_language(parsed)
return make_table(data) .. require("Module:TemplateStyles")("Module:font list/style.css")
end
return export