% 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-textflow.sty % TextFlow (双行小注/夹注) support for vertical typesetting % This is a subpackage of luatex_cn % % NOTE: This file must be loaded AFTER vertical keys are defined % \RequirePackage{core/luatex-cn-core-base} \RequirePackage{expl3} \RequirePackage{xparse} \ProvidesExplPackage {core/luatex-cn-core-textflow} {2026/02/18} {0.3.0} {TextFlow (Jiazhu) Support for Vertical Typesetting} % Default variables for textflow (can be set by higher layers like guji/jiazhu) \tl_new:N \l__luatexcn_textflow_default_size_tl \tl_new:N \l__luatexcn_textflow_default_font_tl \tl_new:N \l__luatexcn_textflow_default_color_tl \tl_new:N \l__luatexcn_textflow_default_align_tl \tl_set:Nn \l__luatexcn_textflow_default_align_tl {outward} % TextFlow Command (双行小注/夹注) % Syntax: \TextFlow{content} \makeatletter \keys_define:nn { luatexcn / textflow } { only-column .choice:, only-column / left .code:n = { \tl_set:Nn \l_tmpa_tl {2} }, only-column / right .code:n = { \tl_set:Nn \l_tmpa_tl {1} }, only-column / none .code:n = { \tl_set:Nn \l_tmpa_tl {0} }, only-column .initial:n = none, auto-balance .bool_set:N = \l__luatexcn_textflow_auto_balance_bool, auto-balance .initial:n = true, font-size .tl_set:N = \l__luatexcn_textflow_local_size_tl, font .tl_set:N = \l__luatexcn_textflow_local_font_tl, font-color .tl_set:N = \l__luatexcn_textflow_local_color_tl, align .tl_set:N = \l__luatexcn_textflow_local_align_tl, } \NewDocumentCommand{\TextFlow}{ O{} +m } { \group_begin: % Initialize temp var for mode (0 = balanced/default) \tl_set:Nn \l_tmpa_tl {0} % Initialize local override variables \tl_set:Nn \l__luatexcn_textflow_local_size_tl {} \tl_set:Nn \l__luatexcn_textflow_local_font_tl {} \tl_set:Nn \l__luatexcn_textflow_local_color_tl {} \tl_set:Nn \l__luatexcn_textflow_local_align_tl {} \keys_set:nn { luatexcn / textflow } { #1 } % Capture attribute values using edef BEFORE selectfont % Note: indent values are now inherited from style stack (no longer saved/restored here) \edef\savedblockid{\the\cnverticalblockid} % 1. Determine Font Size (Local > Core Default > Default 0.7) \tl_if_empty:NTF \l__luatexcn_textflow_local_size_tl { \tl_if_empty:NTF \l__luatexcn_textflow_default_size_tl { \tl_set:Nx \l_tmpc_tl { \fp_eval:n { 0.7 * \f@size } pt } } { \tl_set_eq:NN \l_tmpc_tl \l__luatexcn_textflow_default_size_tl } } { \tl_set_eq:NN \l_tmpc_tl \l__luatexcn_textflow_local_size_tl } % 2. Determine Font (Local > Core Default) \tl_if_empty:NTF \l__luatexcn_textflow_local_font_tl { \tl_set_eq:NN \l_tmpb_tl \l__luatexcn_textflow_default_font_tl } { \tl_set_eq:NN \l_tmpb_tl \l__luatexcn_textflow_local_font_tl } % 3. Determine Color (Local > Core Default) \tl_if_empty:NTF \l__luatexcn_textflow_local_color_tl { \tl_set_eq:NN \l_tmpd_tl \l__luatexcn_textflow_default_color_tl } { \tl_set_eq:NN \l_tmpd_tl \l__luatexcn_textflow_local_color_tl } % 4. Determine Align (Local > Core Default) \tl_if_empty:NTF \l__luatexcn_textflow_local_align_tl { \tl_set_eq:NN \l__luatexcn_textflow_resolved_align_tl \l__luatexcn_textflow_default_align_tl } { \tl_set_eq:NN \l__luatexcn_textflow_resolved_align_tl \l__luatexcn_textflow_local_align_tl } % Apply Font and Size \tl_if_empty:NTF \l_tmpb_tl { \fontsize{\l_tmpc_tl}{\l_tmpc_tl}\selectfont } { \setmainfont{\l_tmpb_tl}[RawFeature={+vert,+vrt2}] \fontsize{\l_tmpc_tl}{\l_tmpc_tl}\selectfont } % DO NOT apply color here via \color - it creates pdf_colorstack nodes % that cause color leakage when textflow spans across page boundaries. % Color is handled entirely in the Lua render layer via PDF literals. % Push style to stack and get style ID (Phase 3: Style Stack Management) \edef\textflow_style_id{\lua_now:e { local ~ textflow = require('core.luatex-cn-textflow') local ~ style_id = textflow.push_style( \tl_if_empty:NTF \l_tmpd_tl { nil } { [=[\luaescapestring{\tl_use:N \l_tmpd_tl}]=] }, [=[\luaescapestring{\tl_use:N \l_tmpc_tl}]=], \tl_if_empty:NTF \l_tmpb_tl { nil } { [=[\luaescapestring{\tl_use:N \l_tmpb_tl}]=] }, \tl_if_empty:NTF \l__luatexcn_textflow_resolved_align_tl { nil } { [=[\luaescapestring{\tl_use:N \l__luatexcn_textflow_resolved_align_tl}]=] }, \bool_if:NTF \l__luatexcn_textflow_auto_balance_bool { true } { false } ) tex.print(tostring(style_id)) }} % CRITICAL: Set textflow attribute AFTER selectfont \setluatexattribute\cnverticaljiazhu{1} \setluatexattribute\cnverticaljiazhumode{\l_tmpa_tl} \setluatexattribute\cnverticalstyle{\textflow_style_id} % Restore block ID only (indent inherited from style stack) \cnverticalblockid = \savedblockid\relax #2 % Pop style from stack \lua_now:n { local ~ textflow = require('core.luatex-cn-textflow') textflow.pop_style() } \group_end: } \NewDocumentCommand{\DanHangJiaZhu}{ O{right} +m } { \TextFlow[only-column=#1]{#2} } % TextFlow Environment - Allows command expansion (e.g., \平抬) % Same functionality as \TextFlow command but content is not captured \NewDocumentEnvironment{TextFlowEnv}{ O{} } { \group_begin: % Initialize temp var for mode (0 = balanced/default) \tl_set:Nn \l_tmpa_tl {0} % Initialize local override variables \tl_set:Nn \l__luatexcn_textflow_local_size_tl {} \tl_set:Nn \l__luatexcn_textflow_local_font_tl {} \tl_set:Nn \l__luatexcn_textflow_local_color_tl {} \tl_set:Nn \l__luatexcn_textflow_local_align_tl {} \keys_set:nn { luatexcn / textflow } { #1 } % Capture attribute values using edef BEFORE selectfont \edef\savedblockid{\the\cnverticalblockid} % 1. Determine Font Size (Local > Core Default > Default 0.7) \tl_if_empty:NTF \l__luatexcn_textflow_local_size_tl { \tl_if_empty:NTF \l__luatexcn_textflow_default_size_tl { \tl_set:Nx \l_tmpc_tl { \fp_eval:n { 0.7 * \f@size } pt } } { \tl_set_eq:NN \l_tmpc_tl \l__luatexcn_textflow_default_size_tl } } { \tl_set_eq:NN \l_tmpc_tl \l__luatexcn_textflow_local_size_tl } % 2. Determine Font (Local > Core Default) \tl_if_empty:NTF \l__luatexcn_textflow_local_font_tl { \tl_set_eq:NN \l_tmpb_tl \l__luatexcn_textflow_default_font_tl } { \tl_set_eq:NN \l_tmpb_tl \l__luatexcn_textflow_local_font_tl } % 3. Determine Color (Local > Core Default) \tl_if_empty:NTF \l__luatexcn_textflow_local_color_tl { \tl_set_eq:NN \l_tmpd_tl \l__luatexcn_textflow_default_color_tl } { \tl_set_eq:NN \l_tmpd_tl \l__luatexcn_textflow_local_color_tl } % 4. Determine Align (Local > Core Default) \tl_if_empty:NTF \l__luatexcn_textflow_local_align_tl { \tl_set_eq:NN \l__luatexcn_textflow_resolved_align_tl \l__luatexcn_textflow_default_align_tl } { \tl_set_eq:NN \l__luatexcn_textflow_resolved_align_tl \l__luatexcn_textflow_local_align_tl } % Apply Font and Size \tl_if_empty:NTF \l_tmpb_tl { \fontsize{\l_tmpc_tl}{\l_tmpc_tl}\selectfont } { \setmainfont{\l_tmpb_tl}[RawFeature={+vert,+vrt2}] \fontsize{\l_tmpc_tl}{\l_tmpc_tl}\selectfont } % Push style to stack and get style ID \edef\textflow_style_id{\lua_now:e { local ~ textflow = require('core.luatex-cn-textflow') local ~ style_id = textflow.push_style( \tl_if_empty:NTF \l_tmpd_tl { nil } { [=[\luaescapestring{\tl_use:N \l_tmpd_tl}]=] }, [=[\luaescapestring{\tl_use:N \l_tmpc_tl}]=], \tl_if_empty:NTF \l_tmpb_tl { nil } { [=[\luaescapestring{\tl_use:N \l_tmpb_tl}]=] }, \tl_if_empty:NTF \l__luatexcn_textflow_resolved_align_tl { nil } { [=[\luaescapestring{\tl_use:N \l__luatexcn_textflow_resolved_align_tl}]=] }, \bool_if:NTF \l__luatexcn_textflow_auto_balance_bool { true } { false } ) tex.print(tostring(style_id)) }} % Set textflow attributes \setluatexattribute\cnverticaljiazhu{1} \setluatexattribute\cnverticaljiazhumode{\l_tmpa_tl} \setluatexattribute\cnverticalstyle{\textflow_style_id} % Restore block ID \cnverticalblockid = \savedblockid\relax } { % Pop style from stack \lua_now:n { local ~ textflow = require('core.luatex-cn-textflow') textflow.pop_style() } \group_end: } \makeatother \ExplSyntaxOff% % ============================================================ % Chinese aliases / 中文别名 % ============================================================ % Simplified Chinese / 简体 \NewCommandCopy{\文本流}{\TextFlow} \NewEnvironmentCopy{文本流环境}{TextFlowEnv} % Traditional Chinese / 繁体 \NewEnvironmentCopy{文本流環境}{TextFlowEnv} % ============================================================ % Chinese key aliases / 中文 Key 别名 % ============================================================ \ExplSyntaxOn \keys_define:nn { luatexcn / textflow } { % 简体 单列 .meta:n = { only-column = #1 }, 自动均衡 .bool_set:N = \l__luatexcn_textflow_auto_balance_bool, 字号 .tl_set:N = \l__luatexcn_textflow_local_size_tl, 字体 .tl_set:N = \l__luatexcn_textflow_local_font_tl, 字体颜色 .tl_set:N = \l__luatexcn_textflow_local_color_tl, 对齐 .tl_set:N = \l__luatexcn_textflow_local_align_tl, % 繁体(与简体不同形的) 單列 .meta:n = { only-column = #1 }, 自動均衡 .bool_set:N = \l__luatexcn_textflow_auto_balance_bool, 字號 .tl_set:N = \l__luatexcn_textflow_local_size_tl, 字體 .tl_set:N = \l__luatexcn_textflow_local_font_tl, 字體顏色 .tl_set:N = \l__luatexcn_textflow_local_color_tl, 對齊 .tl_set:N = \l__luatexcn_textflow_local_align_tl, } \ExplSyntaxOff \endinput%