漢文訓読文を表示するためのモジュールです。

使用法

編集

例:

{{#invoke:Kanbun|kanbun|
桃之夭夭{タル}\O灼灼{タリ}其{ノ}華\\
之{ノ}子于{キ}帰{グ}\O宜[二]{シカラン}其{ノ}室家[一]{ニ}\\\\

桃之{ノ}夭夭{タル}\O有{リ}[レ]蕡{タル}其{ノ}実\\
之{ノ}子于{キ}帰{グ}\O宜{シカラン}[二]其{ノ}家室{ニ}[一]\\\\

桃之夭夭{タル}\O其{ノ}葉蓁蓁{タリ}\\
之{ノ}子于{キ}帰{グ}\O宜[二]{シカラン}其{ノ}家人[一]{ニ}
}}

出力:

タルタリ
シカラン

タルタル
シカラン

タルタリ
シカラン

{}で囲んだ部分は送り仮名や読み仮名を表し、[]で囲んだ部分は返り点を表します。 入力中の改行は漢字の区切り部分であれば出力に影響しません。出力で改行を挿入する場合は、\\を使用します。

特別な記号

  • \\:改行を挿入する。
  • \O:空白を挿入する。
  • \H:ハイフンを含む場合に使用する。

local p = {}

local kunten = {
    ["レ"] = "㆑", -- 返り点には U+3191からU+319Fを使う。
    ["一"] = "㆒",
    ["二"] = "㆓",
    ["三"] = "㆔",
    ["四"] = "㆕",
    ["上"] = "㆖",
    ["中"] = "㆗",
    ["下"] = "㆘",
    ["甲"] = "㆙",
    ["乙"] = "㆚",
    ["丙"] = "㆛",
    ["丁"] = "㆜",
    ["天"] = "㆝",
    ["地"] = "㆞",
    ["人"] = "㆟",
    ["一レ"] = "㆒㆑",
    ["上レ"] = "㆖㆑",
    ["甲レ"] = "㆙㆑",
    ["天レ"] = "㆝㆑"
}

local function parse(input)
    local i = 1 -- 最初のバイト位置
    local j = 1 -- 最後のバイト位置
    local res
    return function()
        while j <= #input do
            i = j

            local byte1 = input:byte(j)

            if byte1 == 0x20 or byte1 == 0xa then -- 空白と改行
                j = j + 1

            elseif byte1 == 0x5c then -- バックスラッシュ
                local next_char = input:sub(j + 1, j + 1)

                if next_char == "\\" then
                    res = input:sub(i, j + 1)
                    j = j + 2
                    return res

                elseif next_char == "O" then
                    res = input:sub(i, j + 1)
                    j = j + 2
                    return res

                elseif next_char == "H" then
                    j = j + 1
                    next_char = input:sub(j + 1, j + 1)
                    while next_char == "[" or next_char == "{" do
                        if next_char == "[" then
                            j = input:find("]", j, true)
                        elseif next_char == "{" then
                            j = input:find("}", j, true)
                        end
                        next_char = input:sub(j + 1, j + 1)
                    end
                    res = input:sub(i, j)
                    j = j + 1
                    return res

                else -- begin 又は end
                    j = input:find("{", j, true)
                    next_char = input:sub(j, j)
                    while next_char == "{" do
                        if next_char == "{" then
                            j = input:find("}", j, true)
                            next_char = input:sub(j + 1, j + 1)
                        end
                    end
                    res = input:sub(i, j)
                    j = j + 1
                    return res
                end
            else
                
                if input:sub(i,i+2, true) == "「"  then
                    j = i + 3
                end

                if input:sub(i, i + 2, true) == "『" then
                    j = i + 3
                end

                -- 文字のバイト
                if byte1 >= 0xF0 then
                    -- 4バイト文字
                    j = j + 3
                elseif byte1 >= 0xE0 then
                    -- 3バイト文字
                    j = j + 2
                elseif byte1 >= 0xC0 then
                    -- 2バイト文字
                    j = j + 1
                else
                    -- 1バイト文字?
                end

                -- IVS
                local byteo = string.byte(input, j+1)
                if byteo >= 0xF0 then
                    local byte2 = string.byte(input, j + 2)
                    local byte3 = string.byte(input, j + 3)
                    local byte4 = string.byte(input, j + 4)

                    local codepoint = (byteo - 0xF0) * 0x40000 + (byte2 - 0x80) * 0x1000 + (byte3 - 0x80) * 0x40 + (byte4 - 0x80)
                    if codepoint >= 0xE0100 and codepoint <= 0xE01EF then
                        j = j + 4
                    end
                end


                if input:sub(j + 1, j + 6) == "。」" then
                    j = j + 6
                end

                if input:sub(j + 1, j + 6) == "。』" then
                    j = j + 6
                end

                if input:sub(j + 1, j + 3) == "。" then
                    j = j + 3
                end

                if input:sub(j + 1, j + 3) == "、" then
                    j = j + 3
                end

                if input:sub(j + 1, j + 3) == "」" then
                    j = j + 3
                end

                if input:sub(j + 1, j + 3) == "』" then
                    j = j + 3
                end

                local next_char = input:sub(j + 1, j + 1)

                while next_char == "[" or next_char == "{" or next_char == "(" do
                    if next_char == "[" then
                        j = input:find("]", j, true)

                    elseif next_char == "{" then
                        j = input:find("}", j, true)

                    elseif next_char == "(" then
                        j = input:find(")", j, true)
                    end
                    next_char = input:sub(j + 1, j + 1)
                end


                if input:sub(j + 1, j + 6) == "。」" then
                    j = j + 6
                end

                if input:sub(j + 1, j + 6) == "。』" then
                    j = j + 6
                end

                if input:sub(j + 1, j + 3) == "。" then
                    j = j + 3
                end

                if input:sub(j + 1, j + 3) == "、" then
                    j = j + 3
                end

                if input:sub(j + 1, j + 3) == "」" then
                    j = j + 3
                end

                if input:sub(j + 1, j + 3) == "』" then
                    j = j + 3
                end

                res = input:sub(i, j)
                j = j + 1

                return res
            end
        end
    end
end

local function hyphen(hyphen_text, shift, style)
    local kanzi, yomigana, kaeriten
    kanzi = hyphen_text:match("\\H%{(.-)%}")
    hyphen_text = hyphen_text:gsub("\\H%{.-%}", "")
    yomigana = hyphen_text:match("%{(.-)%}")
    kaeriten = hyphen_text:match("%[(.-)%]")
    kanzi = string.format('<ruby><rb>%s</rb><rt>%s</rt></ruby>', kanzi, yomigana)
    return string.format(
        '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; margin-top: 0.4em; margin-bottom: 0.5em; bottom: -%sem; %s">%s',
        shift, style, kanzi) ..
               string.format('<span style="position: absolute; font-size: 50%%; top: 1.8em; left: -0.25em;">%s</span>',
            kaeriten)
end

function p.kanbun(frame)
    local input_text = frame.args[1]
    input_text = input_text:gsub("{", "{")
    input_text = input_text:gsub("}", "}")
    input_text = input_text:gsub("[", "[")
    input_text = input_text:gsub("]", "]")
    input_text = input_text:gsub("。", "。")
    input_text = input_text:gsub("、", "、")
    input_text = input_text:gsub("(", "(")
    input_text = input_text:gsub(")", ")")
    input_text = input_text:gsub("「", "「")
    input_text = input_text:gsub("」", "」")
    local html_output =
        '<div style="writing-mode: vertical-rl; font-size: 150%; white-space: nowrap; font-family: serif">'
    local shift = 0
    local style = ""

    for char in parse(input_text) do
        local kanzi = char:match("^(.-)[\\%[%{]") or char
        kanzi = kanzi:gsub("。", "")
        kanzi = kanzi:gsub("、", "")
        kanzi = kanzi:gsub("「", "")
        kanzi = kanzi:gsub("」", "")
        kanzi = kanzi:gsub("『", "")
        kanzi = kanzi:gsub("』", "")
        local okurigana = char:match("{(.-)}")
        local kaeriten = char:match("%[(.-)%]")
        local saidoku = char:match("%((.-)%)")
        local symbol = char:match("\\(H)") or char:match("\\(begin)") or char:match("\\(end)") or char:match("\\(.)")

        if symbol == "O" then
            html_output = html_output .. '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; margin-top: 0em; margin-bottom: 0.9em;"></span>'
        end

        if symbol == "\\" then
            html_output = html_output .. "<br>"
            shift = 0

        elseif symbol == "begin" then
            local style = char:match("\\begin{(.-)}")
            style = style:gsub("~", " ")
            html_output = html_output ..
                              string.format('<span style="display: inline-bock; padding-right:0.5em; %s">', style)

        elseif symbol == "end" then
            html_output = html_output .. "</span>"

        elseif symbol == "H" or kanzi == "H" then
            html_output = html_output .. hyphen("\\" .. char, shift, style)

        else
            -- 漢字

            html_output = html_output .. string.format(
                '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; margin-top: 0.4em; margin-bottom: 0.5em; bottom: -%sem; %s">%s',
                shift, style, kanzi)

            if char:find("。」") then
                html_output = html_output ..
                                  [[<span style="position: absolute; bottom: -1em;">。</span><span style="position: absolute; bottom: -1.25em;">」</span>]]
            end
            if char:find("。』") then
                html_output = html_output ..
                                  [[<span style="position: absolute; bottom: -1em;">。</span><span style="position: absolute; bottom: -1.25em;">』</span>]]
            end
            if char:find("。") then
                html_output = html_output .. [[<span style="position: absolute; bottom: -1em;">。</span>]]
            end

            if char:find("、") then
                html_output = html_output .. [[<span style="position: absolute; bottom: -1em;">、</span>]]
            end

            if char:find("「") then
                html_output = html_output .. [[<span style="position: absolute; top: -1em;">「</span>]]
            end

            if char:find("」") then
                html_output = html_output .. [[<span style="position: absolute; bottom: -1em;">」</span>]]
            end

            if char:find("『") then
                html_output = html_output .. [[<span style="position: absolute; top: -1em;">『</span>]]
            end

            if char:find("』") then
                html_output = html_output .. [[<span style="position: absolute; bottom: -1em;">』</span>]]
            end

            -- 返り点
            if kaeriten then
                kaeriten = kunten[kaeriten] or kaeriten

                if kaeriten == "㆒㆑" then
                    html_output = html_output ..
                                      [[<span style="position: absolute; font-size: 50%; bottom: -0.6em; left: 0.25em;">㆒</span><span style="position: absolute; font-size: 50%; bottom: -0.95em; left: 0.25em;">㆑</span>]]

                elseif kaeriten == "㆖㆑" then
                    html_output = html_output ..
                                      [[<span style="position: absolute; font-size: 50%; bottom: -0.9em; left: 0.25em; transform: scaleY(0.6);">㆖</span><span style="position: absolute; font-size: 50%; bottom: -1.3em; left: 0.25em; transform: scaleY(0.6);">㆑</span>]]

                elseif kaeriten == "㆙㆑" then
                    html_output = html_output ..
                                      [[<span style="position: absolute; font-size: 50%; bottom: -0.9em; left: 0.25em; transform: scaleY(0.6);">㆙</span><span style="position: absolute; font-size: 50%; bottom: -1.3em; left: 0.25em; transform: scaleY(0.6);">㆑</span>]]

                elseif kaeriten == "㆝㆑" then
                    html_output = html_output ..
                                      [[<span style="position: absolute; font-size: 50%; bottom: -0.9em; left: 0.25em; transform: scaleY(0.6);">㆝</span><span style="position: absolute; font-size: 50%; bottom: -1.3em; left: 0.25em; transform: scaleY(0.6);">㆑</span>]]

                else
                    html_output = html_output ..
                                      string.format(
                            '<span style="position: absolute; font-size: 50%%; bottom: -1em; left: 0.25em;">%s</span>',
                            kaeriten)
                end

            end

            -- 送り仮名
            if okurigana then

                -- ひらがなとカタカナの個数を計算
                local hiraganumber = 0
                local katakanumber = 0

                local i = 1
                while i <= #okurigana do
                    local byte = string.byte(okurigana, i)
                    local second = string.byte(okurigana, i + 1) or 0
                    local third = string.byte(okurigana, i + 2) or 0

                    local codepoint = ((byte % 16) * 2 ^ 12) + ((second % 64) * 2 ^ 6) + (third % 64)

                    if codepoint >= 0x3041 and codepoint <= 0x309F then
                        hiraganumber = hiraganumber + 1
                    elseif codepoint >= 0x30A0 and codepoint <= 0x30FF then
                        katakanumber = katakanumber + 1
                    end

                    i = i + 3
                end

                -- 位置の調整
                local bottom = nil
                local top = nil

                if hiraganumber == 0 and katakanumber == 5 then
                    bottom = "-1"
                    local first_line = okurigana:sub(1, 9)
                    local second_line = okurigana:sub(10)

                    html_output = html_output .. string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: %sem; right: -1.5em; white-space: nowrap;">%s</span>',
                        bottom, first_line)

                    html_output = html_output .. string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: %sem; right: -0.5em; white-space: nowrap;">%s</span>',
                        bottom, second_line)

                else

                    if hiraganumber == 0 and katakanumber == 1 then
                        bottom = "-0.5"

                    elseif hiraganumber == 0 and katakanumber == 2 then
                        bottom = "-1"

                    elseif hiraganumber == 0 and katakanumber == 3 then
                        bottom = "-2"

                    elseif hiraganumber == 0 and katakanumber == 4 then
                        bottom = "-2.5"

                    elseif hiraganumber == 1 and katakanumber == 0 then
                        bottom = "0.5"

                    elseif hiraganumber == 2 and katakanumber == 0 then
                        bottom = "0"

                    elseif hiraganumber == 3 and katakanumber == 0 then
                        bottom = "-0.5"

                    elseif hiraganumber == 4 and katakanumber == 0 then
                        bottom = "-1"

                    elseif hiraganumber == 1 and katakanumber == 1 then
                        bottom = "-0.5"

                    elseif hiraganumber + katakanumber >= 5 then
                        top = "0"
                        shift = shift + 0.5 * (hiraganumber + katakanumber - 4)

                    else
                        bottom = "-1"
                    end

                    if bottom then
                        html_output = html_output .. string.format(
                            '<span style="position: absolute; font-size: 50%%; bottom: %sem; right: -0.5em; white-space: nowrap;">%s</span>',
                            bottom, okurigana)
                    else
                        html_output = html_output .. string.format(
                            '<span style="position: absolute; font-size: 50%%; top: %sem; right: -0.5em; white-space: nowrap;">%s</span>',
                            top, okurigana)
                    end
                end
            end

            if saidoku then
                local hiraganumber = 0
                local katakanumber = 0

                local i = 1
                while i <= #saidoku do
                    local byte = string.byte(saidoku, i)
                    local second = string.byte(saidoku, i + 1) or 0
                    local third = string.byte(saidoku, i + 2) or 0

                    local codepoint = ((byte % 16) * 2 ^ 12) + ((second % 64) * 2 ^ 6) + (third % 64)

                    if codepoint >= 0x3041 and codepoint <= 0x309F then
                        hiraganumber = hiraganumber + 1
                    elseif codepoint >= 0x30A0 and codepoint <= 0x30FF then
                        katakanumber = katakanumber + 1
                    end

                    i = i + 3
                end

                local bottom = nil
                local top = nil

                if hiraganumber == 0 and katakanumber == 1 then
                    bottom = "-0.5"

                elseif hiraganumber == 0 and katakanumber == 2 then
                    bottom = "-1"

                elseif hiraganumber == 0 and katakanumber == 3 then
                    bottom = "-2"

                elseif hiraganumber == 0 and katakanumber == 4 then
                    bottom = "-2.5"

                elseif hiraganumber == 1 and katakanumber == 0 then
                    bottom = "0.5"

                elseif hiraganumber == 2 and katakanumber == 0 then
                    bottom = "0"

                elseif hiraganumber == 3 and katakanumber == 0 then
                    bottom = "-0.5"

                elseif hiraganumber == 4 and katakanumber == 0 then
                    bottom = "-1"

                elseif hiraganumber == 1 and katakanumber == 1 then
                    bottom = "-0.5"

                elseif hiraganumber + katakanumber >= 5 then
                    top = "0"
                    shift = shift + 0.5 * (hiraganumber + katakanumber - 4)

                else
                    bottom = "-1"
                end

                if bottom then
                    html_output = html_output .. string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: %sem; left: -0.5em; white-space: nowrap;">%s</span>',
                        bottom, saidoku)
                else
                    html_output = html_output .. string.format(
                        '<span style="position: absolute; font-size: 50%%; top: %sem; left: -0.5em; white-space: nowrap;">%s</span>',
                        top, saidoku)
                end

            end

            html_output = html_output .. "</span>"
        end

    end

    html_output = html_output .. "</div>"

    return html_output
end

return p