%% Copyright 2022--2025 Yuanhao Chen
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
%   http://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
% This work has the LPPL maintenance status `maintained'.
% 
% The Current Maintainer of this work is Yuanhao Chen.
%
% This work consists of the files kanbun.sty, kanbun.lua,
% kanbun-example.tex and kanbun.tex.
% 
\NeedsTeXFormat{LaTeX2e}[2021/06/01]
\RequirePackage{expl3,xparse,l3keys2e,ifluatex}
\ProvidesExplClass{kanbun}
    {2025/3/17} {1.3} {漢文の訓点文の組版}

\ExplSyntaxOn
\cs_generate_variant:Nn \str_if_eq:nnTF { V }
\cs_generate_variant:Nn \str_case:nnTF { V }
\cs_generate_variant:Nn \str_case:nnTF { f }

% vars used in options
\@ifpackageloaded{luatexja-fontspec}{
    \tl_set:Nn \kanbun_rubyfontcmd_default { \addjfontfeatures{RawFeature={+ruby}} }
    \tl_set:Nn \kanbun_fontcmd_default { \addjfontfeatures{RawFeature={+trad}} }
}{
    \tl_set:Nn \kanbun_rubyfontcmd_default {}
    \tl_set:Nn \kanbun_fontcmd_default {}
}

% options
\keys_define:nn { kanbun }
    {    
        , scale .fp_set:N = \kanbun_scale
        , scale .initial:n = { 2 }
        , fontcmd .tl_set:N = \kanbun_fontcmd
        , fontcmd .initial:n = \kanbun_fontcmd_default
        , rubyfontcmd .tl_set:N = \kanbun_rubyfontcmd
        , rubyfontcmd .initial:n = \kanbun_rubyfontcmd_default
        , unit .dim_set:N = \kanbun_zw
        , unit .initial:n = { 1em }
        , yokoaki .fp_set:N = \kanbun_yokoaki
        , yokoaki .initial:n = { 2 }
        , tateaki .fp_set:N = \kanbun_tateaki
        , tateaki .initial:n = { 2 }
        , okuriintrusion .fp_set:N = \kanbun_okuriintrusion
        , okuriintrusion .initial:n = { 1 }
        , kumi .str_set:N = \kanbun_kumi
        , kumi .initial:n = { aki }
        , aki .code:n = \keys_set:nn { kanbun } { kumi = aki }
        , beta .code:n = \keys_set:nn { kanbun } { kumi = beta }
    }
\ProcessKeysOptions { kanbun }
\DeclareDocumentCommand \setkanbun { m } { 
    \keys_set:nn { kanbun } { #1 } 
}

% other vars
\tl_set:Nn \kanbun_rubyfontsize_rubyfontcmd { \kanbun_rubyfontcmd\fontsize{\fp_eval:n {1/\kanbun_scale}\kanbun_zw}{0pt}\selectfont }
\newlength{\kanbun_furilen}
\newlength{\kanbun_okurilen}
\newlength{\kanbun_furiokuri_firstline_len}
\int_new:N \kanbun_furiokuri_firstline_start_index
\tl_set:Nn \kanbun_tateten_width { 1/30 }
\tl_set:Nn \kanbunzwtosp { \fp_eval:n { \dim_ratio:nn { 1 \kanbun_zw } { 1 sp } } }
\tl_set:Nn \kanbun_unit_glue { 
    \str_if_eq:VnTF \kanbun_kumi { aki } {
        \penalty0
    }
    {
        \hskip 0\kanbun_zw plus 0.25\kanbun_zw minus 0\kanbun_zw
    }
}
\tl_set:Nn \kanbun_punct_glue { 
    \hskip 0\kanbun_zw plus 0.5\kanbun_zw minus 0\kanbun_zw
}

% \kanjiunit and other macros to be used in a \kanjiunit
\NewDocumentCommand { \kanbun_ensure_height } { m m }
    {
        \vbox to #1{\vss{#2}\vss}
    }
\newbox\kanbun_current_kanjiunit
\NewDocumentCommand { \kanjiunit } { m m m m m m }
    {
        \tl_set:Nn \ninojiten_punct {}
        \tl_set:Nn \right_punct {}
        \tl_set:Nn \central_punct {}
        \process_punct{ #4 }
        \tl_set:Nn \kanbun_central_punct_box {
            \kanbun_ensure_height{\kanbun_zw} {
                \str_if_eq:VnTF \kanbun_kumi { aki } 
                {
                    \hbox{\kern \kanbun_zw{\hbox to \fp_eval:n { \kanbun_tateaki/\kanbun_scale }\kanbun_zw{\hss{\central_punct}\hss}}}
                }
                {
                    \hbox{\kern \kanbun_zw{\central_punct}}
                }
            }
        }
        \tl_set:Nn \kanbun_left_punct_phantom {
            \kanbun_ensure_height{\kanbun_zw} {
                \str_if_eq:VnTF \kanbun_kumi { aki } { }
                {
                    \str_if_eq:nnTF { #2 } { } { } {
                        \hbox{\phantom{\hbox to 0.5\kanbun_zw{\hss#2\hfil}}}
                    }
                }
            }
        }
        \tl_set:Nn \kanbun_right_punct_box {
            \kanbun_ensure_height{\kanbun_zw} {
                \vfill % make sure punct stay on baseline in yoko direction (xeCJK)
                \str_if_eq:VnTF \right_punct { } { } {
                    \hbox{
                        \kern \kanbun_zw
                        \bool_if:NTF \kanbun_first_right_punct_is_kagi {
                            \phantom{ \kanbun_rubyfontsize_rubyfontcmd\kaeriten{#5} }
                        } { }
                        \right_punct
                    }
                }
            }
        }
        % 
        \tl_set:Nn \unit_content {
            \kanbun_left_punct_phantom % compensate for width of left punct
            \vbox{
                \bool_set_true:N \kanbun_is_right_kana
                \kanbun_ensure_height{ 0pt }{\hbox{\kanbun_rubyfontsize_rubyfontcmd{#1}}} % right kana
                \nointerlineskip %
                \vspace*{\fp_eval:n { 0.5/\kanbun_scale }\kanbun_zw}
                \kanbun_ensure_height{\kanbun_zw}{\llap{#2}} % left punct
                \nointerlineskip %
                \vspace*{-1 \kanbun_zw}
                \kanbun_ensure_height{\kanbun_zw}{
                        \hbox to \kanbun_zw{\hss{#3}\hss}
                } % kanji
                \nointerlineskip %
                \vspace*{-1 \kanbun_zw}
                \kanbun_ensure_height{\fp_eval:n { 1/\kanbun_scale }\kanbun_zw}{\hbox{\kern \kanbun_zw{\ninojiten_punct}}} % ninojiten
                \nointerlineskip %
                \vspace*{\fp_eval:n { -1/\kanbun_scale }\kanbun_zw}
                \kanbun_right_punct_box % right punct
                \nointerlineskip %
                \vspace*{-\kanbun_zw}
                \kanbun_central_punct_box % central punct
                \nointerlineskip %
                \vspace*{ \fp_eval:n { -1/\kanbun_scale }\kanbun_zw }
                \kanbun_ensure_height{\fp_eval:n { 1/\kanbun_scale }\kanbun_zw}{\hbox{\kanbun_rubyfontsize_rubyfontcmd\kern \kanbun_zw{\kaeriten{#5}}}} % kaeriten
                \nointerlineskip %
                \vspace*{\fp_eval:n { 0.5/\kanbun_scale }\kanbun_zw}
                \bool_set_false:N \kanbun_is_right_kana
                \kanbun_ensure_height{ 0pt }{\hbox{\kanbun_rubyfontsize_rubyfontcmd{#6}}} % left kana
            }
        }
        \str_if_eq:VnTF \kanbun_kumi { aki } {
            \setbox\kanbun_current_kanjiunit \hbox to \kanbun_zw{ \unit_content \hss}
        }{
            \setbox\kanbun_current_kanjiunit \hbox{ \unit_content }
        }
        % 
        % output
        \str_if_eq:VnTF \kanbun_kumi { aki } { }
            {
                \str_if_eq:nnTF { #2 } { } { } {
                    \kanbun_punct_glue
                }
            }
        \nobreak
        \str_if_eq:VnTF \kanbun_kumi { aki } 
            {
                \usebox\kanbun_current_kanjiunit
                \hskip\fp_eval:n { \kanbun_tateaki/\kanbun_scale }\kanbun_zw
            }
            {
                \discretionary{
                    \usebox\kanbun_current_kanjiunit
                    \kern\dim_eval:n { \kanbun_zw - \box_wd:N \kanbun_current_kanjiunit }
                }{
                    % 
                }{
                    \usebox\kanbun_current_kanjiunit
                }
            }
        \nobreak
        \str_if_eq:VnTF \kanbun_kumi { aki } { }
            {
                \str_if_eq:VnTF \right_punct { } { } {
                    \kanbun_punct_glue
                }
            }
        \nobreak
        \kanbun_unit_glue
    }
\cs_new:Nn \kanbun_dim_to_fp_and_round:n {
    \fp_eval:n { round( \dim_to_fp:n { #1 }, 0 ) }
}
\cs_new:Nn \kanbun_typeset_normal_kana:nn {
    \dim_compare:nNnTF { \kanbun_furilen } < { \fp_eval:n { 1-\kanbun_okuriintrusion/\kanbun_scale }\kanbun_zw }
        { \makebox[\fp_eval:n { 1-\kanbun_okuriintrusion/\kanbun_scale }\kanbun_zw][l]{ #1 } }
        { #1 }
    #2
}
\cs_new:Nn \kanbun_typeset_long_kana_by_raising_okurigana:nn {
        \makebox[ \dim_eval:n { \fp_eval:n { 1 + \kanbun_tateaki / \kanbun_scale }\kanbun_zw - \kanbun_okurilen } ][l]{ #1 }
        #2
}
\cs_new:Nn \kanbun_typeset_long_kana:n {
    \str_set:Nn \kanbun_furiokuri_all { #1 }
    \int_set:Nn \kanbun_furiokuri_firstline_start_index { -1 }
    \settowidth{ \kanbun_furiokuri_firstline_len }{ }
    % 
    \fp_while_do:nn { 
        \kanbun_dim_to_fp_and_round:n { \kanbun_furiokuri_firstline_len } < \kanbun_dim_to_fp_and_round:n { \fp_eval:n { (\kanbun_okuriintrusion + \kanbun_tateaki) / \kanbun_scale }\kanbun_zw }
        % \kanbun_dim_to_fp_and_round:n { \kanbun_furiokuri_firstline_len } < \kanbun_dim_to_fp_and_round:n { ( \kanbun_furilen + \kanbun_okurilen ) / 2 } 
    } {
        \int_decr:N \kanbun_furiokuri_firstline_start_index
        \settowidth{ \kanbun_furiokuri_firstline_len }{ \str_range:Nnn \kanbun_furiokuri_all { \kanbun_furiokuri_firstline_start_index } { -1 } }
    }
    % output
    \rlap{\raisebox{ \bool_if:NTF \kanbun_is_right_kana { } { - } \fp_eval:n { 1 / \kanbun_scale }\kanbun_zw }[0pt][0pt]{
        \kern \fp_eval:n { .5/\kanbun_scale }\kanbun_zw
        \kern \fp_to_dim:n { min( 0, \kanbun_dim_to_fp_and_round:n { \fp_eval:n { 2 * (\kanbun_okuriintrusion + \kanbun_tateaki) / \kanbun_scale }\kanbun_zw - \kanbun_furilen - \kanbun_okurilen } ) }
        \str_range:Nnn \kanbun_furiokuri_all { 1 } { \kanbun_furiokuri_firstline_start_index - 1 }
    }}
    \kern \fp_eval:n { 1-\kanbun_okuriintrusion/\kanbun_scale }\kanbun_zw \str_range:Nnn \kanbun_furiokuri_all { \kanbun_furiokuri_firstline_start_index } { -1 } 
}
\NewDocumentCommand { \furiokuri } { m m }
    {
        \settowidth{ \kanbun_furilen }{ #1 }
        \settowidth{ \kanbun_okurilen }{ #2 }

        \str_if_eq:VnTF \kanbun_kumi { aki } 
            { 
                \fp_compare:nTF { \kanbun_dim_to_fp_and_round:n { \kanbun_furilen + \kanbun_okurilen } > \kanbun_dim_to_fp_and_round:n {\fp_eval:n { 1 + \kanbun_tateaki / \kanbun_scale }\kanbun_zw} } {
                    \kanbun_typeset_long_kana:n { #1 #2 }
                } {
                    \fp_compare:nTF { \kanbun_dim_to_fp_and_round:n { \kanbun_okurilen } >= \kanbun_dim_to_fp_and_round:n { \fp_eval:n { (\kanbun_okuriintrusion + \kanbun_tateaki) / \kanbun_scale }\kanbun_zw } } {
                        \fp_compare:nTF { \kanbun_dim_to_fp_and_round:n { \kanbun_okurilen } <= \kanbun_dim_to_fp_and_round:n { \fp_eval:n { 1 + (\kanbun_tateaki - 1) / \kanbun_scale }\kanbun_zw } } {
                            \kanbun_typeset_long_kana_by_raising_okurigana:nn { #1 } { #2 }
                        } {
                            \kanbun_typeset_long_kana:n { #1 #2 }
                        }
                    } {
                        \kanbun_typeset_normal_kana:nn { #1 } { #2 }
                    }
                }
            }
            {
                \kanbun_typeset_normal_kana:nn { #1 } { #2 }
            }
    }
\NewDocumentCommand { \kaeriten } { m }
    {
        \tl_set:Nn \kaeriten_output { #1 }
        \sys_if_engine_xetex:TF {} {
            \platex_if_direction_tate:TF {
                \str_case:VnTF \kaeriten_output {
                    % When using U+30EC: KATAKANA LETTER RE
                    { 一レ } { \tl_set:Nn \kaeriten_output { \kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw 一\kern \fp_eval:n {-0.75/\kanbun_scale}\kanbun_zw レ } }
                    { 甲レ } { \tl_set:Nn \kaeriten_output { 甲\kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw レ } }
                    { 上レ } { \tl_set:Nn \kaeriten_output { 上\kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw レ } }
                    { 天レ } { \tl_set:Nn \kaeriten_output { 天\kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw レ } }
                    % When using U+3191: IDEOGRAPHIC ANNOTATION REVERSE MARK
                    { 一㆑ } { \tl_set:Nn \kaeriten_output { \kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw 一\kern \fp_eval:n {-0.75/\kanbun_scale}\kanbun_zw ㆑ } }
                    { 甲㆑ } { \tl_set:Nn \kaeriten_output { 甲\kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw ㆑ } }
                    { 上㆑ } { \tl_set:Nn \kaeriten_output { 上\kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw ㆑ } }
                    { 天㆑ } { \tl_set:Nn \kaeriten_output { 天\kern \fp_eval:n {-0.25/\kanbun_scale}\kanbun_zw ㆑ } }
                } {} {}
            } {}
        }
        \tl_use:N \kaeriten_output
    }
\NewDocumentCommand { \process_punct_map } { m } {
    \str_case:nnTF { #1 } {
        { — } { \tl_put_right:Nn \central_punct { \tateten } }
        { ― } { \tl_put_right:Nn \central_punct { \tateten } }
        { ㆐ } { \tl_put_right:Nn \central_punct { \tateten } }
        % { ： } { \tl_put_right:Nn \central_punct { \hbox to \kanbun_zw{\hss{：}\hss} } }
        % { ・ } { \tl_put_right:Nn \central_punct { \hbox to \kanbun_zw{\hss{・}\hss} } }
        { ： } { \tl_put_right:Nn \right_punct { \hbox to 0.5\kanbun_zw{\hss{：}\hss} } }
        { ・ } { \tl_put_right:Nn \right_punct { \hbox to 0.5\kanbun_zw{\hss{・}\hss} } }
        { … } { \tl_put_right:Nn \central_punct { \hbox to \kanbun_zw{\hss{…}\hss} } }
        { ！ } { \tl_put_right:Nn \central_punct { \hbox to \kanbun_zw{\hss{！}\hss} } }
        { ？ } { \tl_put_right:Nn \central_punct { \hbox to \kanbun_zw{\hss{？}\hss} } }
        { 〻 } { 
            \tl_put_right:Nn \ninojiten_punct { \ninojiten } 
            \tl_put_right:Nn \right_punct { \phantom{\ninojiten} }
        }
    } {} {
        \tl_put_right:Nn \right_punct { \hbox to 0.5\kanbun_zw{\hfil#1\hss} } 
    }
}
% \bool_new:N 
\NewDocumentCommand { \process_punct } { m }
    {
        \bool_set_false:N \kanbun_first_right_punct_is_kagi
        \str_case:fnTF { \str_range:nnn { #1 } { 1 } { 1 } } {
            { 」 } { \bool_set_true:N \kanbun_first_right_punct_is_kagi }
            { 』 } { \bool_set_true:N \kanbun_first_right_punct_is_kagi }
        } { } { }
        \str_map_function:nN { #1 } \process_punct_map
    }
\NewDocumentCommand { \multifuriokuri } { O{2\kanbun_zw} m m }
    {
        \kanbun_rubyfontcmd
        \kern \fp_eval:n { -(#1) }\kanbun_zw
        \makebox[\fp_eval:n { (#1) + 1 - \kanbun_okuriintrusion/\kanbun_scale }\kanbun_zw][s]{#2}
        #3
    }

% these commands are used as punct
\NewDocumentCommand { \tateten } {} 
    {
        \str_if_eq:VnTF \kanbun_kumi { aki } {
            \makebox[\fp_eval:n {\kanbun_tateaki/\kanbun_scale}\kanbun_zw][c]{
                \rule[\fp_eval:n {-\kanbun_tateten_width/2}\kanbun_zw]{\fp_eval:n {\kanbun_tateaki/\kanbun_scale}\kanbun_zw}{\fp_eval:n {\kanbun_tateten_width}\kanbun_zw}
            }
        }{
            \makebox[\fp_eval:n {1/\kanbun_scale}\kanbun_zw][c]{
                \rule[\fp_eval:n {-\kanbun_tateten_width/2}\kanbun_zw]{\fp_eval:n {1/\kanbun_scale}\kanbun_zw}{\fp_eval:n {\kanbun_tateten_width}\kanbun_zw}
            }
        }
    }
\NewDocumentCommand { \ninojiten } {} 
    { \kanbun_rubyfontsize_rubyfontcmd 〻 }

% corrects fontsize of ruby base
% \NewDocumentCommand { \kanbunfont } {} { \parindent=0pt\kanbun_fontcmd\fontsize{\kanbun_zw}{\fp_eval:n {1+\kanbun_yokoaki/\kanbun_scale}\kanbun_zw}\selectfont}
\NewDocumentCommand { \kanbunfont } {} { \kanbun_fontcmd\fontsize{\kanbun_zw}{\fp_eval:n {1+\kanbun_yokoaki/\kanbun_scale}\kanbun_zw}\selectfont}
\NewDocumentEnvironment { kanjipar } { +b }
    {
        {
            \kanbunfont
            #1
            \par
        }
    }   {}

% for LuaLaTeX to parse Kanbun annotation
\NewDocumentCommand{\matchkana}{ m }{
    \regex_set:Nn \c_kana_regex {[\x{3040}-\x{30FA}\x{30FC}-\x{30FF}\x{31F0}-\x{31FF}\x{FF66}-\x{FF9F}\x{1B100}-\x{1B122}\x{1AFF0}-\x{1AFFF}\x{1B000}-\x{1B0FF}\x{1B130}-\x{1B16F}]}
    \regex_match:NnTF \c_kana_regex { #1 } { \bool_set_true:N \g_kana_bool } { \bool_set_false:N \g_kana_bool }
}
% 
\ExplSyntaxOff
% 
\ifluatex
% verbatim reader adapted from https://tex.stackexchange.com/a/361759
\directlua{
verb_table = {}
function store_lines (str)
    if string.find (str , "\noexpand\\EndKanbun" ) then
        luatexbase.remove_from_callback (
            "process_input_buffer" , "store_lines")
        return "\\newif\\ifcontinue\\continuetrue\\directlua { co = coroutine.create(main_loop) }\\loop\\directlua{ ok,b=coroutine.resume(co) tex.sprint(b) }\\ifcontinue\\repeat"
    else
        table.insert(verb_table, str)
    end
    return ""
end
function register_verbatim ()
    verb_table = {}
    luatexbase.add_to_callback(
        "process_input_buffer" , store_lines , "store_lines")
end
% require main loop
require("kanbun.lua")
}
\def\Kanbun{\directlua{
    kanbunzwtosp = \luaescapestring{\kanbunzwtosp}
    register_verbatim()
}}
\def\createcatcodes{%
    \bgroup%
        \catcode`\\=12 \catcode`\{=12 \catcode`\}=12%
        \catcode`\$=12 \catcode`\&=12 \catcode`\^^M=13%
        \catcode`\#=12 \catcode`\^=12 \catcode`\_=12%
        \catcode`\ =13 \catcode`\~=12 \catcode`\%=12%
        \savecatcodetable 1%
    \egroup%
}
\createcatcodes
\bgroup%
    \catcode`\^^M=13\gdef^^M{\quitvmode\par}%
    \catcode`\ = 13\gdef {\quitvmode\Space}%
\egroup%
\def\Space{ }
% 
\fi
