% Copyright 2026 Open-Guji (https://github.com/open-guji) % % Licensed under the Apache License, Version 2.0 (the "License"); % you may not use this file except in compliance with the License. % You may obtain a copy of the License at % % http://www.apache.org/licenses/LICENSE-2.0 % % Unless required by applicable law or agreed to in writing, software % distributed under the License is distributed on an "AS IS" BASIS, % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % See the License for the specific language governing permissions and % limitations under the License. % luatex-cn-banxin.sty % 古籍版心与鱼尾功能支持 % 这是 luatex_cn 的子包 % \RequirePackage{expl3} \RequirePackage{xparse} \ProvidesExplPackage {banxin/luatex-cn-banxin} {2026/08/07} {0.4.1} {Ancient Book Banxin Support} \lua_now:e { package.loaded["banxin.luatex-cn-banxin-main"]~=~nil; banxin~=~require("banxin.luatex-cn-banxin-main"); } % ============================================================================= % Variable Declarations % ============================================================================= % Pre-rendered section boxes (global, survives groups) \box_new:N \g__luatexcn_banxin_upper_box \box_new:N \g__luatexcn_banxin_middle_box \box_new:N \g__luatexcn_banxin_lower_box % Banxin on/off \bool_new:N \l__luatexcn_banxin_on_bool % Banxin layout ratios \tl_new:N \l__luatexcn_banxin_upper_ratio_tl \tl_new:N \l__luatexcn_banxin_middle_ratio_tl % Banxin divider \bool_new:N \l__luatexcn_banxin_divider_bool % Yuwei \bool_new:N \l__luatexcn_banxin_upper_yuwei_bool \bool_new:N \l__luatexcn_banxin_lower_yuwei_bool % Upper section variables (版心上部) \tl_new:N \l__luatexcn_banxin_upper_section_text_tl \tl_new:N \l__luatexcn_banxin_upper_section_font_size_tl \tl_new:N \l__luatexcn_banxin_upper_section_grid_height_tl \tl_new:N \l__luatexcn_banxin_upper_section_align_tl \tl_new:N \l__luatexcn_banxin_upper_section_top_padding_tl \tl_new:N \l__luatexcn_banxin_upper_section_bottom_padding_tl \tl_new:N \l__luatexcn_banxin_upper_section_bg_color_tl \tl_new:N \l__luatexcn_banxin_upper_section_font_color_tl % Middle section variables (版心中部) \tl_new:N \l__luatexcn_banxin_middle_section_text_tl \tl_new:N \l__luatexcn_banxin_middle_section_font_size_tl \tl_new:N \l__luatexcn_banxin_middle_section_grid_height_tl \tl_new:N \l__luatexcn_banxin_middle_section_top_margin_tl \int_new:N \l__luatexcn_banxin_middle_section_cols_int \tl_new:N \l__luatexcn_banxin_middle_section_align_tl \tl_new:N \l__luatexcn_banxin_middle_section_bg_color_tl \tl_new:N \l__luatexcn_banxin_middle_section_font_color_tl % Page number variables \bool_new:N \l__luatexcn_banxin_page_number_show_bool \tl_new:N \l__luatexcn_banxin_page_number_font_size_tl \tl_new:N \l__luatexcn_banxin_page_number_grid_height_tl \tl_new:N \l__luatexcn_banxin_page_number_align_tl \tl_new:N \l__luatexcn_banxin_page_number_bottom_padding_tl % Lower section variables (版心下部) \tl_new:N \l__luatexcn_banxin_lower_section_text_tl \tl_new:N \l__luatexcn_banxin_lower_section_font_size_tl \tl_new:N \l__luatexcn_banxin_lower_section_grid_height_tl \tl_new:N \l__luatexcn_banxin_lower_section_bottom_margin_tl \tl_new:N \l__luatexcn_banxin_lower_section_top_padding_tl \tl_new:N \l__luatexcn_banxin_lower_section_bottom_padding_tl \tl_new:N \l__luatexcn_banxin_lower_section_align_tl \tl_new:N \l__luatexcn_banxin_lower_section_bg_color_tl \tl_new:N \l__luatexcn_banxin_lower_section_font_color_tl % Banxin-level style overrides (forwarded from unknown handler) \tl_new:N \l__luatexcn_banxin_style_font_size_tl \tl_new:N \l__luatexcn_banxin_style_font_color_tl \tl_new:N \l__luatexcn_banxin_style_font_tl \tl_new:N \l__luatexcn_banxin_style_grid_height_tl \tl_new:N \l__luatexcn_banxin_style_grid_width_tl % ============================================================================= % Sub-Namespace Definitions % ============================================================================= % upper-section sub-namespace (版心上部) \keys_define:nn { luatexcn / banxin / upper-section } { font-size .tl_set:N = \l__luatexcn_banxin_upper_section_font_size_tl, font-size .initial:n = {}, grid-height .tl_set:N = \l__luatexcn_banxin_upper_section_grid_height_tl, grid-height .initial:n = {}, align .tl_set:N = \l__luatexcn_banxin_upper_section_align_tl, align .initial:n = {center}, top-padding .tl_set:N = \l__luatexcn_banxin_upper_section_top_padding_tl, top-padding .initial:n = {2pt}, bottom-padding .tl_set:N = \l__luatexcn_banxin_upper_section_bottom_padding_tl, bottom-padding .initial:n = {10pt}, background-color .tl_set:N = \l__luatexcn_banxin_upper_section_bg_color_tl, background-color .initial:n = {}, font-color .tl_set:N = \l__luatexcn_banxin_upper_section_font_color_tl, font-color .initial:n = {}, } % middle-section sub-namespace (版心中部) \keys_define:nn { luatexcn / banxin / middle-section } { font-size .tl_set:N = \l__luatexcn_banxin_middle_section_font_size_tl, font-size .initial:n = {}, grid-height .tl_set:N = \l__luatexcn_banxin_middle_section_grid_height_tl, grid-height .initial:n = {}, top-margin .tl_set:N = \l__luatexcn_banxin_middle_section_top_margin_tl, top-margin .initial:n = {20pt}, cols .int_set:N = \l__luatexcn_banxin_middle_section_cols_int, cols .initial:n = 1, align .tl_set:N = \l__luatexcn_banxin_middle_section_align_tl, align .initial:n = {center}, background-color .tl_set:N = \l__luatexcn_banxin_middle_section_bg_color_tl, background-color .initial:n = {}, font-color .tl_set:N = \l__luatexcn_banxin_middle_section_font_color_tl, font-color .initial:n = {}, } % page-number sub-namespace \keys_define:nn { luatexcn / banxin / page-number } { show .bool_set:N = \l__luatexcn_banxin_page_number_show_bool, show .initial:n = true, font-size .tl_set:N = \l__luatexcn_banxin_page_number_font_size_tl, font-size .initial:n = {14pt}, grid-height .tl_set:N = \l__luatexcn_banxin_page_number_grid_height_tl, grid-height .initial:n = {}, align .tl_set:N = \l__luatexcn_banxin_page_number_align_tl, align .initial:n = {right-bottom}, bottom-padding .tl_set:N = \l__luatexcn_banxin_page_number_bottom_padding_tl, bottom-padding .initial:n = {10pt}, } % lower-section sub-namespace (版心下部) \keys_define:nn { luatexcn / banxin / lower-section } { font-size .tl_set:N = \l__luatexcn_banxin_lower_section_font_size_tl, font-size .initial:n = {10pt}, grid-height .tl_set:N = \l__luatexcn_banxin_lower_section_grid_height_tl, grid-height .initial:n = {}, align .tl_set:N = \l__luatexcn_banxin_lower_section_align_tl, align .initial:n = {right}, bottom-margin .tl_set:N = \l__luatexcn_banxin_lower_section_bottom_margin_tl, bottom-margin .initial:n = {5pt}, top-padding .tl_set:N = \l__luatexcn_banxin_lower_section_top_padding_tl, top-padding .initial:n = {2pt}, bottom-padding .tl_set:N = \l__luatexcn_banxin_lower_section_bottom_padding_tl, bottom-padding .initial:n = {10pt}, background-color .tl_set:N = \l__luatexcn_banxin_lower_section_bg_color_tl, background-color .initial:n = {}, font-color .tl_set:N = \l__luatexcn_banxin_lower_section_font_color_tl, font-color .initial:n = {}, } % ============================================================================= % Main Banxin Key Definitions % ============================================================================= \keys_define:nn { luatexcn / banxin } { % Banxin on/off banxin .bool_set:N = \l__luatexcn_banxin_on_bool, banxin .initial:n = false, % Banxin layout ratios banxin-upper-ratio .tl_set:N = \l__luatexcn_banxin_upper_ratio_tl, banxin-upper-ratio .initial:n = {0.28}, banxin-middle-ratio .tl_set:N = \l__luatexcn_banxin_middle_ratio_tl, banxin-middle-ratio .initial:n = {0.56}, % Banxin divider banxin-divider .bool_set:N = \l__luatexcn_banxin_divider_bool, banxin-divider .initial:n = true, % Yuwei upper-yuwei .bool_set:N = \l__luatexcn_banxin_upper_yuwei_bool, upper-yuwei .initial:n = true, lower-yuwei .bool_set:N = \l__luatexcn_banxin_lower_yuwei_bool, lower-yuwei .initial:n = true, % Middle section text content (used by \chapter command) middle-section-text .tl_set:N = \l__luatexcn_banxin_middle_section_text_tl, middle-section-text .initial:n = {}, % Nested key handlers - detect text vs key-value pairs upper-section .code:n = { \tl_if_in:nnTF { #1 } { = } { \keys_set:nn { luatexcn / banxin / upper-section } { #1 } } { \tl_set:Nn \l__luatexcn_banxin_upper_section_text_tl { #1 } } }, middle-section .code:n = { \keys_set:nn { luatexcn / banxin / middle-section } { #1 } }, page-number .code:n = { \keys_set:nn { luatexcn / banxin / page-number } { #1 } }, lower-section .code:n = { \tl_if_in:nnTF { #1 } { = } { \keys_set:nn { luatexcn / banxin / lower-section } { #1 } } { \tl_set:Nn \l__luatexcn_banxin_lower_section_text_tl { #1 } } }, % Forward unknown keys: try settings first, then style unknown .code:n = { \exp_args:Nnx \keys_if_exist:nnTF { luatexcn / settings } { \l_keys_key_str } { \keys_set:nx { luatexcn / settings } { \l_keys_key_str = { \exp_not:n {#1} } } } { \exp_args:Nnx \keys_if_exist:nnTF { luatexcn / style } { \l_keys_key_str } { \keys_set:nx { luatexcn / style } { \l_keys_key_str = { \exp_not:n {#1} } } } { } } }, } % ============================================================================= % Setup Command % ============================================================================= \NewDocumentCommand{\banxinSetup}{ m } { % Clear style scratch variables before parsing (unknown keys forward to style) \__luatexcn_style_init_and_parse:n {} \keys_set:nn { luatexcn / banxin } { #1 } % Save any style overrides into banxin-specific variables \tl_if_empty:NF \l__luatexcn_style_size_tl { \tl_set_eq:NN \l__luatexcn_banxin_style_font_size_tl \l__luatexcn_style_size_tl } \tl_if_empty:NF \l__luatexcn_style_color_tl { \tl_set_eq:NN \l__luatexcn_banxin_style_font_color_tl \l__luatexcn_style_color_tl } \tl_if_empty:NF \l__luatexcn_style_font_tl { \tl_set_eq:NN \l__luatexcn_banxin_style_font_tl \l__luatexcn_style_font_tl } \tl_if_empty:NF \l__luatexcn_style_grid_height_tl { \tl_set_eq:NN \l__luatexcn_banxin_style_grid_height_tl \l__luatexcn_style_grid_height_tl } \tl_if_empty:NF \l__luatexcn_style_grid_width_tl { \tl_set_eq:NN \l__luatexcn_banxin_style_grid_width_tl \l__luatexcn_style_grid_width_tl } % Update _G.banxin.enabled for early access by content.setup \lua_now:e { _G.banxin.setup({ enabled~=~\bool_if:NTF~\l__luatexcn_banxin_on_bool~{true}~{false} }) } } % ============================================================================= % Title / Chapter Synchronization % ============================================================================= % 1. Title → upper section text \cs_if_exist:NTF \title { \NewCommandCopy \__luatexcn_banxin_orig_title \title \RenewDocumentCommand \title { m } { \__luatexcn_banxin_orig_title { #1 } \tl_set:Nn \l__luatexcn_banxin_upper_section_text_tl { #1 } } } { \NewDocumentCommand \title { m } { \gdef\@title{#1} \tl_set:Nn \l__luatexcn_banxin_upper_section_text_tl { #1 } } } % 2. Chapter → middle section text \cs_if_exist:NTF \chapter { \NewCommandCopy \__luatexcn_banxin_orig_chapter \chapter \RenewDocumentCommand \chapter { s o m } { \IfBooleanTF {#1} { \IfValueTF {#2} { \__luatexcn_banxin_orig_chapter* [#2] {#3} } { \__luatexcn_banxin_orig_chapter* {#3} } } { \IfValueTF {#2} { \__luatexcn_banxin_orig_chapter [#2] {#3} } { \__luatexcn_banxin_orig_chapter {#3} } } \IfValueTF {#2} { \tl_set:Nn \l__luatexcn_banxin_middle_section_text_tl { #2 } } { \tl_set:Nn \l__luatexcn_banxin_middle_section_text_tl { #3 } } } } { \DeclareDocumentCommand \chapter { s o m } { \IfValueTF {#2} { \tl_set:Nn \l__luatexcn_banxin_middle_section_text_tl { #2 } } { \tl_set:Nn \l__luatexcn_banxin_middle_section_text_tl { #3 } } } } % ============================================================================ % CJK Key Aliases (中文键值别名) % ============================================================================ % --- upper-section sub-namespace --- \keys_define:nn { luatexcn / banxin / upper-section } { 字号 .tl_set:N = \l__luatexcn_banxin_upper_section_font_size_tl, 格高 .tl_set:N = \l__luatexcn_banxin_upper_section_grid_height_tl, 对齐 .tl_set:N = \l__luatexcn_banxin_upper_section_align_tl, 底色 .tl_set:N = \l__luatexcn_banxin_upper_section_bg_color_tl, 字体颜色 .tl_set:N = \l__luatexcn_banxin_upper_section_font_color_tl, } % --- middle-section sub-namespace --- \keys_define:nn { luatexcn / banxin / middle-section } { 字号 .tl_set:N = \l__luatexcn_banxin_middle_section_font_size_tl, 格高 .tl_set:N = \l__luatexcn_banxin_middle_section_grid_height_tl, 上留白 .tl_set:N = \l__luatexcn_banxin_middle_section_top_margin_tl, 列数 .int_set:N = \l__luatexcn_banxin_middle_section_cols_int, 列數 .int_set:N = \l__luatexcn_banxin_middle_section_cols_int, 对齐 .tl_set:N = \l__luatexcn_banxin_middle_section_align_tl, 對齊 .tl_set:N = \l__luatexcn_banxin_middle_section_align_tl, 底色 .tl_set:N = \l__luatexcn_banxin_middle_section_bg_color_tl, 字体颜色 .tl_set:N = \l__luatexcn_banxin_middle_section_font_color_tl, } % --- page-number sub-namespace --- \keys_define:nn { luatexcn / banxin / page-number } { 显示 .bool_set:N = \l__luatexcn_banxin_page_number_show_bool, 顯示 .bool_set:N = \l__luatexcn_banxin_page_number_show_bool, 字号 .tl_set:N = \l__luatexcn_banxin_page_number_font_size_tl, 格高 .tl_set:N = \l__luatexcn_banxin_page_number_grid_height_tl, 对齐 .tl_set:N = \l__luatexcn_banxin_page_number_align_tl, } % --- lower-section sub-namespace --- \keys_define:nn { luatexcn / banxin / lower-section } { 字号 .tl_set:N = \l__luatexcn_banxin_lower_section_font_size_tl, 格高 .tl_set:N = \l__luatexcn_banxin_lower_section_grid_height_tl, 对齐 .tl_set:N = \l__luatexcn_banxin_lower_section_align_tl, 下留白 .tl_set:N = \l__luatexcn_banxin_lower_section_bottom_margin_tl, 底色 .tl_set:N = \l__luatexcn_banxin_lower_section_bg_color_tl, 字体颜色 .tl_set:N = \l__luatexcn_banxin_lower_section_font_color_tl, } % --- main banxin namespace --- \keys_define:nn { luatexcn / banxin } { 版心 .bool_set:N = \l__luatexcn_banxin_on_bool, 版心上比 .tl_set:N = \l__luatexcn_banxin_upper_ratio_tl, 版心中比 .tl_set:N = \l__luatexcn_banxin_middle_ratio_tl, 版心分隔线 .bool_set:N = \l__luatexcn_banxin_divider_bool, 版心分隔線 .bool_set:N = \l__luatexcn_banxin_divider_bool, 上鱼尾 .bool_set:N = \l__luatexcn_banxin_upper_yuwei_bool, 上魚尾 .bool_set:N = \l__luatexcn_banxin_upper_yuwei_bool, 下鱼尾 .bool_set:N = \l__luatexcn_banxin_lower_yuwei_bool, 下魚尾 .bool_set:N = \l__luatexcn_banxin_lower_yuwei_bool, 中部文字 .tl_set:N = \l__luatexcn_banxin_middle_section_text_tl, 上部 .meta:n = { upper-section = {#1} }, 中部 .meta:n = { middle-section = {#1} }, 下部 .meta:n = { lower-section = {#1} }, 页码 .meta:n = { page-number = {#1} }, 頁碼 .meta:n = { page-number = {#1} }, } % ============================================================================ % Pre-render upper section as box (called from \end{BodyText}) % ============================================================================ % Typesets upper-section content into a global box for use in banxin render. % Must be called after content sync (fonts loaded, _G.content available). \cs_new_protected:Nn \__luatexcn_banxin_typeset_sections: { \bool_if:NT \l__luatexcn_banxin_on_bool { \tl_if_empty:NTF \l__luatexcn_banxin_upper_section_text_tl { \box_gclear:N \g__luatexcn_banxin_upper_box } { % Calculate banxin dimensions from Lua globals % (available after content sync in \end{BodyText}) % Height = upper_region - border (padding handled inside TextBox) \tl_set:Ne \l_tmpa_tl { \lua_now:e { local~c~=~_G.content~or~{} local~ratio~=~tonumber(token.get_macro("l__luatexcn_banxin_upper_ratio_tl"))~or~0.28~ local~region_h~=~(c.content_height~or~0)~*~ratio~ local~border_t~=~c.border_on~and~(c.border_thickness~or~0)~or~0~ local~h~=~region_h~-~border_t~ if~h~<~0~then~h~=~region_h~end~ tex.print(tostring(math.floor(h~+~0.5))~..~"sp") } } \tl_set:Ne \l_tmpb_tl { \lua_now:e { local~c~=~_G.content~or~{} tex.print(tostring(c.banxin_width~or~655360)~..~"sp") } } % Build TextBox options (border=false to avoid inheriting global border) % top-padding / bottom-padding are passed as distribute-padding % (affects fill distribution range, NOT per-cell padding) \tl_set:Nn \l__luatexcn_banxin_tb_opts_tl { height = \l_tmpa_tl , grid-width = \l_tmpb_tl , border = false , box-align = fill , fill-padding-top = \l__luatexcn_banxin_upper_section_top_padding_tl , fill-padding-bottom = \l__luatexcn_banxin_upper_section_bottom_padding_tl } \tl_if_empty:NF \l__luatexcn_banxin_upper_section_font_size_tl { \tl_put_right:Ne \l__luatexcn_banxin_tb_opts_tl { ,~ font-size = \l__luatexcn_banxin_upper_section_font_size_tl } } \tl_if_empty:NF \l__luatexcn_banxin_upper_section_grid_height_tl { \tl_put_right:Ne \l__luatexcn_banxin_tb_opts_tl { ,~ grid-height = \l__luatexcn_banxin_upper_section_grid_height_tl } } % Typeset into global box using TextBox pipeline % If user text already contains \TextBox / \文本框, use it directly \vbox_gset:Nn \g__luatexcn_banxin_upper_box { \__luatexcn_banxin_typeset_content:NnN \l__luatexcn_banxin_upper_section_text_tl { \l__luatexcn_banxin_tb_opts_tl } \l__luatexcn_banxin_upper_section_text_tl } % Store box in Lua for render stage \lua_now:e { _G.banxin~=~_G.banxin~or~{} _G.banxin.store_upper_section_box( \int_value:w \g__luatexcn_banxin_upper_box ) } } % --- Middle section pre-render --- \tl_if_empty:NTF \l__luatexcn_banxin_middle_section_text_tl { \box_gclear:N \g__luatexcn_banxin_middle_box } { % Height = middle available height (middle region - yuwei - top_margin) \tl_set:Ne \l_tmpa_tl { \lua_now:e { local~layout~=~require("banxin.luatex-cn-banxin-layout") local~c~=~_G.content~or~{} local~upper_yw~=~token.get_macro("l__luatexcn_banxin_upper_yuwei_bool") local~lower_yw~=~token.get_macro("l__luatexcn_banxin_lower_yuwei_bool") local~top_margin_str~=~token.get_macro("l__luatexcn_banxin_middle_section_top_margin_tl") local~constants~=~require("core.luatex-cn-constants") local~top_margin~=~constants.resolve_dimen(top_margin_str,~655360)~or~0 local~h~=~layout.calc_middle_available_height({ content_height~=~c.content_height~or~0, middle_ratio~=~tonumber(token.get_macro("l__luatexcn_banxin_middle_ratio_tl"))~or~0.56, banxin_width~=~c.banxin_width~or~655360, upper_yuwei~=~(upper_yw~==~"\\iftrue"), lower_yuwei~=~(lower_yw~==~"\\iftrue"), top_margin~=~top_margin, }) tex.print(tostring(h)~..~"sp") } } \tl_set:Ne \l_tmpb_tl { \lua_now:e { local~c~=~_G.content~or~{} tex.print(tostring(c.banxin_width~or~655360)~..~"sp") } } \tl_set:Nn \l__luatexcn_banxin_tb_opts_tl { height = \l_tmpa_tl , grid-width = \l_tmpb_tl , border = false } \tl_if_empty:NF \l__luatexcn_banxin_middle_section_font_size_tl { \tl_put_right:Ne \l__luatexcn_banxin_tb_opts_tl { ,~ font-size = \l__luatexcn_banxin_middle_section_font_size_tl } } \tl_if_empty:NF \l__luatexcn_banxin_middle_section_grid_height_tl { \tl_put_right:Ne \l__luatexcn_banxin_tb_opts_tl { ,~ grid-height = \l__luatexcn_banxin_middle_section_grid_height_tl } } \vbox_gset:Nn \g__luatexcn_banxin_middle_box { \__luatexcn_banxin_typeset_content:NnN \l__luatexcn_banxin_middle_section_text_tl { \l__luatexcn_banxin_tb_opts_tl } \l__luatexcn_banxin_middle_section_text_tl } \lua_now:e { _G.banxin~=~_G.banxin~or~{} _G.banxin.store_middle_section_box( \int_value:w \g__luatexcn_banxin_middle_box ) } } % --- Lower section pre-render --- \tl_if_empty:NTF \l__luatexcn_banxin_lower_section_text_tl { \box_gclear:N \g__luatexcn_banxin_lower_box } { % Height = lower_region - border \tl_set:Ne \l_tmpa_tl { \lua_now:e { local~c~=~_G.content~or~{} local~ur~=~tonumber(token.get_macro("l__luatexcn_banxin_upper_ratio_tl"))~or~0.28~ local~mr~=~tonumber(token.get_macro("l__luatexcn_banxin_middle_ratio_tl"))~or~0.56~ local~lr~=~1~-~ur~-~mr~ local~region_h~=~(c.content_height~or~0)~*~lr~ local~border_t~=~c.border_on~and~(c.border_thickness~or~0)~or~0~ local~h~=~region_h~-~border_t~ if~h~<~0~then~h~=~region_h~end~ tex.print(tostring(math.floor(h~+~0.5))~..~"sp") } } \tl_set:Ne \l_tmpb_tl { \lua_now:e { local~c~=~_G.content~or~{} tex.print(tostring(c.banxin_width~or~655360)~..~"sp") } } \tl_set:Nn \l__luatexcn_banxin_tb_opts_tl { height = \l_tmpa_tl , grid-width = \l_tmpb_tl , border = false , box-align = fill , fill-padding-top = \l__luatexcn_banxin_lower_section_top_padding_tl , fill-padding-bottom = \l__luatexcn_banxin_lower_section_bottom_padding_tl } \tl_if_empty:NF \l__luatexcn_banxin_lower_section_font_size_tl { \tl_put_right:Ne \l__luatexcn_banxin_tb_opts_tl { ,~ font-size = \l__luatexcn_banxin_lower_section_font_size_tl } } \tl_if_empty:NF \l__luatexcn_banxin_lower_section_grid_height_tl { \tl_put_right:Ne \l__luatexcn_banxin_tb_opts_tl { ,~ grid-height = \l__luatexcn_banxin_lower_section_grid_height_tl } } \vbox_gset:Nn \g__luatexcn_banxin_lower_box { \__luatexcn_banxin_typeset_content:NnN \l__luatexcn_banxin_lower_section_text_tl { \l__luatexcn_banxin_tb_opts_tl } \l__luatexcn_banxin_lower_section_text_tl } \lua_now:e { _G.banxin~=~_G.banxin~or~{} _G.banxin.store_lower_section_box( \int_value:w \g__luatexcn_banxin_lower_box ) } } } } \tl_new:N \l__luatexcn_banxin_tb_opts_tl % Helper: typeset banxin section content % Always wraps in \TextBox with the given options. % User text may contain \TextBox / \文本框 — these become nested content % processed through the TextBox pipeline with correct banxin dimensions. % #1 = tl var (unused, kept for API compat) % #2 = TextBox options tl % #3 = tl var with the text content \cs_new_protected:Nn \__luatexcn_banxin_typeset_content:NnN { \use:x { \exp_not:N \TextBox [ #2 ] { \exp_not:V #3 } } } % ============================================================================ % CJK Aliases (中文别名) % ============================================================================ % Simplified Chinese / 简体 \NewCommandCopy{\版心设置}{\banxinSetup} % Traditional Chinese / 繁体 \NewCommandCopy{\版心設置}{\banxinSetup} \ExplSyntaxOff% % \endinput%