local export = {}
local CURVATURE = 0.51
local function make_error(message, index)
local specifier_index = math.floor((index - 1) / 4) + 1
error(message .. " (argument #" .. index .. ", specifier #" .. specifier_index .. ")")
end
local function parse_fraction(arg, quality, index)
local num, denom = mw.ustring.match(arg, "^([0-9]+)/([0-9]+)$")
return num / denom
end
local function parse_scalar(arg, quality, index)
arg = mw.text.trim(arg)
local result
if mw.ustring.match(arg, "^[0-9]+/[0-9]+$") then
result = parse_fraction(arg, quality, index)
else
result = tonumber(arg)
end
if result == nil or result < 0 or result > 1 then
return make_error(quality .. " must be a value within [0, 1]", index)
end
return result
end
local function parse_boolean(arg, quality, index)
arg = mw.text.trim(arg)
if arg == "0" then
return 0
elseif arg == "1" then
return 1
else
return make_error(quality .. " must be a boolean", index)
end
end
local function parse_vowel_specifier(args, index)
local vowel = args[index]
local openness = parse_scalar(args[index + 1], "openness", index + 1)
local backness = parse_scalar(args[index + 2], "backness", index + 2)
local roundness = parse_boolean(args[index + 3], "roundness", index + 3)
return {
vowel = vowel,
openness = openness,
backness = backness,
roundness = roundness,
}
end
local function curve(backness, openness, curvature)
return backness * (CURVATURE + (1 - CURVATURE) * (1 - openness)) + openness * (1 - CURVATURE)
end
local function display_chart(vowels, args)
local content = ""
table.sort(vowels, function (a, b) return a.roundness < b.roundness end)
local vowels_merged = {}
local vowels_table = {}
for _, vowel in ipairs(vowels) do
local key = vowel.openness .. ":" .. vowel.backness
if vowels_table[key] then
table.insert(vowels_table[key].variants, vowel.vowel)
else
local new_table = {
backness = vowel.backness,
openness = vowel.openness,
variants = {vowel.vowel}
}
vowels_table[key] = new_table
table.insert(vowels_merged, new_table)
end
end
vowels_table = nil
for _, vowel in ipairs(vowels_merged) do
local y = vowel.openness
local x = curve(vowel.backness, y, CURVATURE)
local parent = mw.html.create('div')
:css('position', 'absolute')
:css('top', (y * 100) .. "%")
:css('left', (x * 100) .. "%")
:css('font-size', '120%;')
:tag('div')
:css('position', 'relative')
:css('left', '-2em')
:css('top', '-1.25em')
:css('width', '3em')
:css('height', '2em')
:css('padding', '0.5em')
:css('text-align', 'center')
for index, phoneme in ipairs(vowel.variants) do
if index > 1 then parent:wikitext(' • ') end
parent = parent
:tag('span')
:attr('class', 'IPA')
:wikitext(phoneme)
:done()
end
content = content .. tostring(parent:allDone())
end
local style = args.style or ""
local title_row
if args.title then
title_row = "\n|-\n! colspan=4 | " .. args.title .. "\n|-\n"
else
title_row = "\n"
end
return [[
{| class="wikitable IPA-vowel-chart" style="]] .. style .. [["]] .. title_row.. [[
! !! class="backness-column" | Front !! class="backness-column" | Central !! class="backness-column" | Back
|-
! class="openness-row" | Close
| colspan="3" rowspan="7" | <div class="content-outer"><div class="content-inner"><div class="content-filler"><div class="trapezoid"></div></div>]] .. content .. [[</div></div>
|-
! class="openness-row" | Near-close
|-
! class="openness-row" | Close-mid
|-
! class="openness-row" | Mid
|-
! class="openness-row" | Open-mid
|-
! class="openness-row" | Near-open
|-
! class="openness-row" | Open
|}
]]
end
function export.show(frame)
local args = frame:getParent().args
local index = 1
local vowels = {}
while args[index] do
if not args[index + 3] then
error("Incomplete vowel specifier")
end
table.insert(vowels, parse_vowel_specifier(args, index))
index = index + 4
end
return display_chart(vowels, { style = args["style"], title = args["title"] })
end
return export