Module:PreDiphic
来自HUIJIA FUN
此模块的文档可以在Module:PreDiphic/doc创建
-- Module:PreDiphic
-- 把罗马字(和Keyman键盘pre_diphic 1.1、Pre-Diphic打字页的打法相同)转换成Pre-Diphic Mono 1.1的私用区字符。
-- 转换规则与MediaWiki:PreDiphicTyper.js的convert()逐条对应。改规则时两边同步。
-- 已经是私用区字符的内容原样保留,所以旧写法也能继续用。
local p = {}
local VO = { a = 0xEE20, e = 0xEE21, o = 0xEE22, i = 0xEE23, u = 0xEE24 }
local CO = {
p = 0xEE00, b = 0xEE01, t = 0xEE02, d = 0xEE03, k = 0xEE04, g = 0xEE05,
r = 0xEE09, n = 0xEE0A, f = 0xEE0B, v = 0xEE0C, s = 0xEE0D, z = 0xEE0E,
l = 0xEE13, m = 0xEE14, q = 0xEE15, j = 0xEE16, x = 0xEE17, y = 0xEE18,
w = 0xEE19, h = 0xEE1A, c = 0xEE1B,
}
local KO = { r = 0xEE25, l = 0xEE26, n = 0xEE27, m = 0xEE28 }
local PO = { [','] = 0xEE30, ['.'] = 0xEE31, ['!'] = 0xEE32, ['?'] = 0xEE33, ['-'] = 0xEE34 }
local NULLC, RV = 0xEE1F, 0xEE29
-- 双字母辅音:前一个辅音 + 后一个字母 → 合成后的辅音
local DI = {
[0xEE02] = { s = 0xEE11 }, -- ts
[0xEE03] = { z = 0xEE12 }, -- dz
[0xEE0D] = { t = 0xEE0F, h = 0xEE08 }, -- st sh
[0xEE0E] = { d = 0xEE10, h = 0xEE07 }, -- zd zh
[0xEE0A] = { g = 0xEE1D }, -- ng
[0xEE1B] = { h = 0xEE06 }, -- ch
}
local function isV(c)
return c ~= nil and c >= 0xEE20 and c <= 0xEE24
end
local function isC(c)
return c ~= nil and c >= 0xEE00 and c <= 0xEE1D
end
-- 罗马字 → 码位数组
function p.codepoints(src)
local o, syl = {}, false
for cp in mw.ustring.gcodepoint(mw.ustring.lower(src)) do
local ch = cp < 128 and string.char(cp) or nil
local last = o[#o]
if ch == ' ' then
if syl then o[#o + 1] = 0x20; syl = false else syl = true end
elseif ch == '\n' then
o[#o + 1] = 10; syl = false
else
local wasSyl = syl
syl = false
if ch and VO[ch] then
if not wasSyl and (isC(last) or isV(last)) then
o[#o + 1] = VO[ch]
else
o[#o + 1] = NULLC; o[#o + 1] = VO[ch]
end
elseif ch and PO[ch] then
o[#o + 1] = PO[ch]
elseif ch and ch:match('%d') then
o[#o + 1] = 0xEE50 + tonumber(ch)
elseif wasSyl then
o[#o + 1] = (ch and CO[ch]) or cp
elseif ch == "'" then
if isV(last) then o[#o + 1] = last end
elseif ch == '^' then
if isC(last) then o[#o + 1] = RV end
elseif ch == 'h' and #o >= 2 and o[#o - 1] == NULLC and last == 0xEE21 then
o[#o] = nil; o[#o] = nil; o[#o + 1] = 0xEE1C -- eh
elseif ch and last and DI[last] and DI[last][ch] then
o[#o] = DI[last][ch]
elseif ch and KO[ch] and (isV(last) or last == RV) then
o[#o + 1] = KO[ch]
elseif ch and CO[ch] then
o[#o + 1] = CO[ch]
else
o[#o + 1] = cp
end
end
end
return o
end
function p.toText(src)
local o = p.codepoints(src)
local parts = {}
for k = 1, #o do parts[k] = mw.ustring.char(o[k]) end
return table.concat(parts)
end
-- 罗马字的显示形式:音节用连字符,词用空格(yo li di pi ka → yo-li di-pi-ka)
function p.romanize(src)
local words = {}
for w in mw.text.trim(src):gsub(' +', '\1'):gmatch('[^\1]+') do
words[#words + 1] = (mw.text.trim(w):gsub(' +', '-'))
end
return table.concat(words, ' ')
end
-- {{#invoke:PreDiphic|text|罗马字}}:只输出转换后的字符
function p.text(frame)
return p.toText(frame.args[1] or '')
end
-- 一个码位是否开始一个新的字格(音节、标点、数字串各占一个字高)
-- 元音、韵尾、还原元音(U+EE20–EE29)都挂在前一个音节上,不另占字格
local function startsCell(c)
return not (c >= 0xEE20 and c <= 0xEE29)
end
local function isDigit(c)
return c >= 0xEE50 and c <= 0xEE59
end
-- 把一个词(码位数组)切成字格;数字从右往左每三位一格
local function cells(word)
local out, k = {}, 1
while k <= #word do
local c = word[k]
if isDigit(c) then
local j = k
while j + 1 <= #word and isDigit(word[j + 1]) do j = j + 1 end
local take = (j - k + 1) % 3
if take == 0 then take = 3 end
local pos = k
while pos <= j do
local cell = {}
for t = pos, pos + take - 1 do cell[#cell + 1] = word[t] end
out[#out + 1] = cell
pos = pos + take
take = 3
end
k = j + 1
elseif #out == 0 or startsCell(c) then
out[#out + 1] = { c }
k = k + 1
else
local cell = out[#out]
cell[#cell + 1] = c
k = k + 1
end
end
return out
end
-- 按每列字格数分列:只在词界换列;一个词比一列还长时才在词中间断开;换行符另起一列
-- 返回 { {nl=是否新段落, words={ {text=文字, n=字格数, cont=是否接着上一列的半个词}, … }}, … }
function p.columns(src, perCol)
local cols, cur, used, newPara = {}, nil, 0, true
local function flush()
if cur and #cur.words > 0 then cols[#cols + 1] = cur end
cur, used = nil, 0
end
local function open()
if not cur then cur = { nl = newPara, words = {} }; newPara = false end
end
local function piece(cps, n, cont)
local parts = {}
for t = 1, #cps do parts[t] = mw.ustring.char(cps[t]) end
cur.words[#cur.words + 1] = { text = table.concat(parts), n = n, cont = cont }
end
local function addWord(word)
local cs = cells(word)
if #cs == 0 then return end
if used > 0 and used + #cs > perCol then flush() end
local buf, n, cont = {}, 0, false
for _, cell in ipairs(cs) do
if used >= perCol then
open(); piece(buf, n, cont); buf, n, cont = {}, 0, true
flush()
end
open()
for _, c in ipairs(cell) do buf[#buf + 1] = c end
n = n + 1
used = used + 1
end
open(); piece(buf, n, cont)
end
local word = {}
for _, c in ipairs(p.codepoints(src)) do
if c == 0x20 then
addWord(word); word = {}
elseif c == 10 then
addWord(word); word = {}; flush(); newPara = true
else
word[#word + 1] = c
end
end
addWord(word)
flush()
return cols
end
-- 每个字格的高度约为0.8em(字高800单位)
local CELL_EM = 0.8
local DEFAULT_PER_COL = 16
-- 每列字数:填了「每列」就用它;否则用「高度」÷字格高度换算(两者都是px时);都没填就是16
local function perColumn(args, size)
local n = tonumber(args['每列'])
if n and n >= 1 then return math.floor(n) end
local h = args['高度'] and tonumber((args['高度']:match('^%s*([%d.]+)%s*px%s*$')))
local fs = size and tonumber((size:match('^%s*([%d.]+)%s*px%s*$')))
if h and fs and fs > 0 then
return math.max(1, math.floor(h / (fs * CELL_EM)))
end
return DEFAULT_PER_COL
end
-- {{PD}}模板的入口:读模板参数,输出HTML
function p.main(frame)
local args = frame:getParent().args
local src = mw.text.trim(args['内容'] or args[1] or '')
if src == '' then return '' end
local vertical = args['方向'] ~= '横排'
local box = mw.html.create('span'):addClass('pd')
-- 默认竖排、32px;填「方向=横排」改为横排,填「字号=」(留空)则跟随正文
local size = args['字号']
if size == nil then size = '32px' end
if size ~= '' then box:css('font-size', size) end
if args['颜色'] and args['颜色'] ~= '' then box:css('color', args['颜色']) end
-- 鼠标悬停显示的罗马字:默认不显示;只有填了「罗马字=…」才显示
local roman = args['罗马字']
if roman and roman ~= '' then box:attr('title', roman) end
if vertical then
-- 竖排:切成固定字数的列;列从左往右排,撞到正文右边就自动换到下面一排
box:addClass('pd-cols')
local maxH = args['高度']
local fixed = (tonumber(args['每列']) ~= nil) or (maxH ~= nil and maxH ~= '')
-- 没填「每列」「高度」时,交给Common.js按屏幕高度重新分列(data-pd-auto);
-- 关掉JS的访客看到的是这里按每列16字分好的结果
if not fixed then box:attr('data-pd-auto', '1') end
for _, col in ipairs(p.columns(src, perColumn(args, size))) do
local c = box:tag('span'):addClass('pd-col')
if col.nl then c:addClass('pd-nl') end
-- 「高度」同时作为每列的高度上限(单位不是px、换算不了时也有效)
if maxH and maxH ~= '' then c:css('max-height', maxH) end
for k, w in ipairs(col.words) do
if k > 1 then c:wikitext(' ') end
local ws = c:tag('span'):addClass('pd-w'):attr('data-n', tostring(w.n))
if w.cont then ws:attr('data-c', '1') end -- 接着上一列的半个词
ws:wikitext(mw.text.nowiki(w.text))
end
end
else
local lines = mw.text.split(p.toText(src), '\n', true)
for k, line in ipairs(lines) do
if k > 1 then box:tag('br') end
box:wikitext(mw.text.nowiki(line))
end
end
return tostring(box)
end
return p
