% 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-core-sidenote.sty % SideNode (侧批) support for vertical typesetting % This is a subpackage of luatex_cn % \RequirePackage{core/luatex-cn-core-base} \RequirePackage{expl3} \RequirePackage{xparse} \ProvidesExplPackage {core/luatex-cn-core-sidenote} {2026/02/18} {0.3.0} {SideNode Support for Vertical Typesetting} % Define keys for SideNode \tl_new:N \l__luatexcn_sidenote_yshift_tl \tl_new:N \l__luatexcn_sidenote_grid_height_tl \tl_new:N \l__luatexcn_sidenote_font_size_tl \tl_new:N \l__luatexcn_sidenote_color_tl \tl_new:N \l__luatexcn_sidenote_padding_top_tl \tl_new:N \l__luatexcn_sidenote_padding_bottom_tl \keys_define:nn { luatexcn / sidenode } { yshift .tl_set:N = \l__luatexcn_sidenote_yshift_tl, yshift .initial:n = 0pt, grid-height .tl_set:N = \l__luatexcn_sidenote_grid_height_tl, font-size .tl_set:N = \l__luatexcn_sidenote_font_size_tl, font-size .initial:n = 10pt, color .tl_set:N = \l__luatexcn_sidenote_color_tl, color .initial:n = red, border-padding-top .tl_set:N = \l__luatexcn_sidenote_padding_top_tl, border-padding-top .initial:n = 0pt, border-padding-bottom .tl_set:N = \l__luatexcn_sidenote_padding_bottom_tl, border-padding-bottom .initial:n = 0pt, } \NewDocumentCommand{\sidenodeSetup}{ m } { \keys_set:nn { luatexcn / sidenode } { #1 } } \NewDocumentCommand{\SideNode}{ O{} +m }{% \group_begin: % Process optional argument: handle positional parameter (plain number → em) \tl_if_empty:nTF { #1 } { % Empty argument: no special processing needed } { % Check if it's a plain number by attempting regex match \regex_match:nnTF { ^\d+(?:\.\d+)?$ } { #1 } { % Plain number: convert to em \tl_set:Nn \l_tmpa_tl { yshift = #1 em } \keys_set:nn { luatexcn / sidenode } { yshift = #1 em } } { % Not a plain number: check if it has '=' \regex_match:nnTF { = } { #1 } { % Contains '=': use as-is (named parameter like color=red) \keys_set:nn { luatexcn / sidenode } { #1 } } { % No '=': treat as positional yshift with units \keys_set:nn { luatexcn / sidenode } { yshift = #1 } } } } % If #1 was empty, no keys to set (use defaults) % Use explicit font size \tl_if_empty:NTF \l__luatexcn_sidenote_font_size_tl { \dim_set:Nn \l_tmpa_dim { \f@size pt } } { \dim_set:Nn \l_tmpa_dim { \__luatexcn_to_dimen:n { \l__luatexcn_sidenote_font_size_tl } } } \fontsize{\l_tmpa_dim}{\l_tmpa_dim}\selectfont % DO NOT use \textcolor here - it creates pdf_colorstack nodes that cause % color leakage when sidenotes span across page boundaries. % Color is handled entirely in the Lua render layer via PDF literals. \hbox_set:Nn \l_tmpa_box {#2}% % Prepare metadata \dim_set:Nn \l_tmpb_dim { \__luatexcn_to_dimen:n { \l__luatexcn_sidenote_grid_height_tl } } % If grid-height not set, default to font size (tight packing) \dim_compare:nF { \l_tmpb_dim > 0pt } { \dim_set_eq:NN \l_tmpb_dim \l_tmpa_dim } % Pass style attributes to Lua (style registration handled in Lua layer) \lua_now:e { vertical_sidenote.register_sidenote(\int_value:w \l_tmpa_box, { yshift = vertical_constants.to_dimen([=[\luaescapestring{\tl_use:N \l__luatexcn_sidenote_yshift_tl}]=]) ~ or ~ 0, grid_height = \int_value:w \l_tmpb_dim, padding_top = vertical_constants.to_dimen([=[\luaescapestring{\tl_use:N \l__luatexcn_sidenote_padding_top_tl}]=]) ~ or ~ 0, padding_bottom = vertical_constants.to_dimen([=[\luaescapestring{\tl_use:N \l__luatexcn_sidenote_padding_bottom_tl}]=]) ~ or ~ 0, font_color = [=[\luaescapestring{\tl_use:N \l__luatexcn_sidenote_color_tl}]=], font_size = [=[\luaescapestring{\dim_use:N \l_tmpa_dim}]=] })}% \group_end: } \ExplSyntaxOff% % ============================================================ % Chinese aliases / 中文别名 % ============================================================ % Simplified Chinese / 简体 \NewCommandCopy{\侧批}{\SideNode} \NewCommandCopy{\CePi}{\SideNode} \NewCommandCopy{\侧批设置}{\sidenodeSetup} % Traditional Chinese / 繁体 \NewCommandCopy{\側批}{\SideNode} \NewCommandCopy{\側批設置}{\sidenodeSetup} % ============================================================ % Chinese key aliases / 中文 Key 别名 % ============================================================ \ExplSyntaxOn \keys_define:nn { luatexcn / sidenode } { % 简体 纵移 .tl_set:N = \l__luatexcn_sidenote_yshift_tl, 格高 .tl_set:N = \l__luatexcn_sidenote_grid_height_tl, 字号 .tl_set:N = \l__luatexcn_sidenote_font_size_tl, 颜色 .tl_set:N = \l__luatexcn_sidenote_color_tl, 边框上填充 .tl_set:N = \l__luatexcn_sidenote_padding_top_tl, 边框下填充 .tl_set:N = \l__luatexcn_sidenote_padding_bottom_tl, % 繁体(与简体不同形的) 縱移 .tl_set:N = \l__luatexcn_sidenote_yshift_tl, 字號 .tl_set:N = \l__luatexcn_sidenote_font_size_tl, 顏色 .tl_set:N = \l__luatexcn_sidenote_color_tl, } \ExplSyntaxOff \endinput%