% 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-footnote.sty % Footnote/Jiaokan (脚注/校勘记) support for vertical typesetting % Mode 1: Endnotes (段末注) - footnotes output at paragraph end % Mode 2: Page footnotes (页下注) - footnotes at page left with separator % \RequirePackage{expl3} \RequirePackage{xparse} \ProvidesExplPackage {core/luatex-cn-footnote} {2026/02/18} {0.3.0} {Footnote/Jiaokan support} % ============================================================================ % Load Lua module and set global utils % ============================================================================ \lua_now:e { require('core.luatex-cn-footnote') vertical_utils = require('util.luatex-cn-utils') } % ============================================================================ % Key-value Configuration % ============================================================================ \tl_new:N \l__luatexcn_footnote_mode_tl \tl_new:N \l__luatexcn_footnote_number_style_tl \tl_new:N \l__luatexcn_footnote_separator_tl \tl_new:N \l__luatexcn_footnote_font_tl \tl_new:N \l__luatexcn_footnote_font_size_tl \tl_new:N \l__luatexcn_footnote_font_color_tl \keys_define:nn { luatexcn / footnote } { mode .tl_set:N = \l__luatexcn_footnote_mode_tl, mode .initial:n = {endnote}, number-style .tl_set:N = \l__luatexcn_footnote_number_style_tl, number-style .initial:n = {lujiao}, separator .tl_set:N = \l__luatexcn_footnote_separator_tl, separator .initial:n = {blank}, font .tl_set:N = \l__luatexcn_footnote_font_tl, font .initial:n = {}, font-size .tl_set:N = \l__luatexcn_footnote_font_size_tl, font-size .initial:n = {0.8em}, font-color .tl_set:N = \l__luatexcn_footnote_font_color_tl, font-color .initial:n = {}, indent .dim_set:N = \l__luatexcn_footnote_indent_dim, indent .initial:n = {1em}, spacing .dim_set:N = \l__luatexcn_footnote_spacing_dim, spacing .initial:n = {0.5em}, } % ============================================================================ % Internal: Apply footnote font size (always from content base font-size) % ============================================================================ \dim_new:N \l__luatexcn_footnote_resolved_size_dim \cs_new_protected:Nn \__luatexcn_footnote_apply_font_size: { % First, temporarily set font to base content size so that relative units % (like 0.8em) resolve correctly against the base, not the current font. \fontsize { \l__luatexcn_content_font_size_tl } { \l__luatexcn_content_font_size_tl } \selectfont % Now resolve the footnote font-size (may contain em units) \dim_set:Nn \l__luatexcn_footnote_resolved_size_dim { \l__luatexcn_footnote_font_size_tl } % Apply the resolved absolute size \fontsize { \dim_use:N \l__luatexcn_footnote_resolved_size_dim } { \dim_use:N \l__luatexcn_footnote_resolved_size_dim } \selectfont } % ============================================================================ % Global Storage (Mode 1) % ============================================================================ \seq_new:N \g__luatexcn_footnote_content_seq \int_new:N \g__luatexcn_footnote_counter_int \box_new:N \l__luatexcn_footnote_temp_box % ============================================================================ % Setup Command (syncs to Lua _G.footnote) % ============================================================================ \NewDocumentCommand{\footnoteSetup}{ m } { \keys_set:nn { luatexcn / footnote } { #1 } \lua_now:e { _G.footnote = _G.footnote~or~{} _G.footnote.mode = "\l__luatexcn_footnote_mode_tl" _G.footnote.number_style = "\l__luatexcn_footnote_number_style_tl" _G.footnote.font_size = "\l__luatexcn_footnote_font_size_tl" _G.footnote.font_color = "\l__luatexcn_footnote_font_color_tl" _G.footnote.font = "\l__luatexcn_footnote_font_tl" } } % ============================================================================ % Internal: Write Footnote Marker (【一】 or ①) % ============================================================================ \cs_new:Nn \__luatexcn_footnote_write_marker:n { \str_case:VnF \l__luatexcn_footnote_number_style_tl { {lujiao} { 【\lua_now:e{ tex.print(vertical_utils.to_chinese_numeral(#1)) }】 } {circled} { \lua_now:e{ tex.print(vertical_utils.to_circled_numeral(#1)) } } } { 【\lua_now:e{ tex.print(vertical_utils.to_chinese_numeral(#1)) }】 } } % ============================================================================ % Internal: Separator (blank space or none) % ============================================================================ \cs_new:Nn \__luatexcn_footnote_separator: { \str_case:VnF \l__luatexcn_footnote_separator_tl { {blank} { \hspace{1em} } {none} { } } { \hspace{1em} } } % ============================================================================ % \Footnote{content} - Store content + write inline marker % Mode 1: Store in expl3 sequence % Mode 2: Register in Lua and write WHATSIT anchor % ============================================================================ \NewDocumentCommand{\Footnote}{ O{} +m } { \group_begin: \int_gincr:N \g__luatexcn_footnote_counter_int \str_if_eq:VnTF \l__luatexcn_footnote_mode_tl {page} { % Mode 2: Register in Lua for page rendering \hbox_set:Nn \l__luatexcn_footnote_temp_box { #2 } \lua_now:e { local~footnote~=~require('core.luatex-cn-footnote') footnote.register_footnote( \int_use:N \l__luatexcn_footnote_temp_box, \int_use:N \g__luatexcn_footnote_counter_int ) } } { % Mode 1: Store in sequence for paragraph-end output \seq_gput_right:Nn \g__luatexcn_footnote_content_seq { #2 } } % Write inline marker with smaller font + right-align \group_begin: \tl_if_empty:NF \l__luatexcn_footnote_font_size_tl { \__luatexcn_footnote_apply_font_size: } \lua_now:e { local~constants~=~require('core.luatex-cn-constants') tex.setattribute(constants.ATTR_HALIGN,~3) } \__luatexcn_footnote_write_marker:n { \int_use:N \g__luatexcn_footnote_counter_int } \group_end: \group_end: } % ============================================================================ % \FlushFootnote - Output all footnotes and reset (Mode 1 only) % ============================================================================ \NewDocumentCommand{\FlushFootnote}{} { \str_if_eq:VnF \l__luatexcn_footnote_mode_tl {page} { \int_compare:nNnT { \seq_count:N \g__luatexcn_footnote_content_seq } > { 0 } { % No separator before footnotes: the column break (from synthetic penalty % or \penalty -10002) already provides visual separation, and \hspace creates % an unwanted empty column in vertical layout. % Group to contain font-size change (prevent leaking to subsequent text) \group_begin: % Apply footnote font-size using absolute value from content base font-size % (avoid relative em units which compound across multiple FlushFootnote calls) \tl_if_empty:NF \l__luatexcn_footnote_font_size_tl { \__luatexcn_footnote_apply_font_size: } \int_step_inline:nn { \seq_count:N \g__luatexcn_footnote_content_seq } { % Forced ATTR_INDENT ensures each footnote column starts indented. % Note: ATTR_COLUMN_BREAK_INDENT on penalty is unreliable because % TeX's paragraph builder may create new penalty nodes without % custom attributes. Forced ATTR_INDENT on glyphs survives flatten. \lua_now:e { local~constants~=~require('core.luatex-cn-constants') local~indent_sp~=~\dim_to_decimal_in_sp:n { \l__luatexcn_footnote_indent_dim } local~grid_h_sp~=~\dim_to_decimal_in_sp:n { \l__luatexcn_content_grid_height_tl } local~cells~=~math.max(1,~math.floor(indent_sp~/~grid_h_sp~+~0.5)) tex.setattribute(constants.ATTR_INDENT,~constants.encode_forced_indent(cells)) } \penalty -10002\relax \__luatexcn_footnote_write_marker:n { ##1 } \seq_item:Nn \g__luatexcn_footnote_content_seq { ##1 } } \group_end: \seq_gclear:N \g__luatexcn_footnote_content_seq \int_gzero:N \g__luatexcn_footnote_counter_int } } } % ============================================================================ % \ResetFootnoteCounter - Reset counter (for Mode 2 per-page reset) % ============================================================================ \NewDocumentCommand{\ResetFootnoteCounter}{} { \int_gzero:N \g__luatexcn_footnote_counter_int } \ExplSyntaxOff% % ============================================================ % Chinese aliases / 中文别名 % ============================================================ % Simplified Chinese / 简体 \NewCommandCopy{\脚注}{\Footnote} \NewCommandCopy{\输出脚注}{\FlushFootnote} \NewCommandCopy{\脚注设置}{\footnoteSetup} \NewCommandCopy{\重置脚注计数}{\ResetFootnoteCounter} % Traditional Chinese / 繁体 \NewCommandCopy{\腳注}{\Footnote} \NewCommandCopy{\輸出腳注}{\FlushFootnote} \NewCommandCopy{\腳注設置}{\footnoteSetup} \NewCommandCopy{\重置腳注計數}{\ResetFootnoteCounter} % ============================================================ % Chinese key aliases / 中文 Key 别名 % ============================================================ \ExplSyntaxOn \keys_define:nn { luatexcn / footnote } { % 简体 模式 .tl_set:N = \l__luatexcn_footnote_mode_tl, 编号样式 .tl_set:N = \l__luatexcn_footnote_number_style_tl, 分隔符 .tl_set:N = \l__luatexcn_footnote_separator_tl, 字体 .tl_set:N = \l__luatexcn_footnote_font_tl, 字号 .tl_set:N = \l__luatexcn_footnote_font_size_tl, 字体颜色 .tl_set:N = \l__luatexcn_footnote_font_color_tl, 缩进 .dim_set:N = \l__luatexcn_footnote_indent_dim, 间距 .dim_set:N = \l__luatexcn_footnote_spacing_dim, % 繁体(与简体不同形的) 編號樣式 .tl_set:N = \l__luatexcn_footnote_number_style_tl, 字體 .tl_set:N = \l__luatexcn_footnote_font_tl, 字號 .tl_set:N = \l__luatexcn_footnote_font_size_tl, 字體顏色 .tl_set:N = \l__luatexcn_footnote_font_color_tl, 縮進 .dim_set:N = \l__luatexcn_footnote_indent_dim, 間距 .dim_set:N = \l__luatexcn_footnote_spacing_dim, } \ExplSyntaxOff \endinput%