%% Copyright 2022 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} {2022/02/13} {1.2} {漢文の訓点文の組版} \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 { { 一レ } { \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 kanbunzwtosp = \luaescapestring{\kanbunzwtosp} require("kanbun.lua") } \def\Kanbun{\directlua{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