% 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. \RequirePackage{core/luatex-cn-core-base} \ProvidesExplPackage{core/luatex-cn-core-content}{2026/10/29} {0.4.3}{Content layout and border support} % Centralized variables for content border settings \bool_new:N \l__luatexcn_content_border_bool \tl_new:N \l__luatexcn_content_border_thickness_tl \tl_new:N \l__luatexcn_content_padding_top_tl \tl_new:N \l__luatexcn_content_padding_bottom_tl \bool_new:N \l__luatexcn_content_outer_border_bool \tl_new:N \l__luatexcn_content_outer_border_thickness_tl \tl_new:N \l__luatexcn_content_outer_border_sep_tl \tl_new:N \l__luatexcn_content_border_color_tl % Grid layout variables \int_new:N \l__luatexcn_content_n_char_per_col_int \tl_new:N \l__luatexcn_content_grid_width_tl \tl_new:N \l__luatexcn_content_grid_height_tl \tl_new:N \l__luatexcn_content_valign_tl \tl_new:N \l__luatexcn_content_font_size_tl \int_new:N \l__luatexcn_content_n_column_int \tl_new:N \l__luatexcn_content_page_columns_tl \tl_new:N \l__luatexcn_content_height_tl \tl_new:N \l__luatexcn_content_col_spacing_tl \tl_new:N \l__luatexcn_content_line_spacing_tl \tl_new:N \l__luatexcn_content_banxin_ratio_tl \tl_new:N \l__luatexcn_content_border_rgb_tl \tl_new:N \l__luatexcn_content_background_rgb_tl \tl_new:N \l__luatexcn_content_font_rgb_tl \tl_new:N \l__luatexcn_content_font_color_tl \tl_new:N \l__luatexcn_content_font_name_tl \tl_new:N \l__luatexcn_content_font_features_tl \tl_new:N \l__luatexcn_content_layout_mode_tl \tl_new:N \l__luatexcn_content_inter_cell_gap_tl \tl_new:N \l__luatexcn_content_cell_height_tl \tl_new:N \l__luatexcn_content_cell_width_tl \tl_new:N \l__luatexcn_content_cell_gap_tl % Flexible column width and spacing (Free Mode) \bool_new:N \l__luatexcn_content_auto_col_width_bool \tl_new:N \l__luatexcn_content_col_spacing_top_tl \tl_new:N \l__luatexcn_content_col_spacing_bottom_tl \tl_new:N \l__luatexcn_content_para_spacing_tl % Digital mode: async vbox + obeylines (each newline = column break) \bool_new:N \l__luatexcn_content_digital_mode_bool % Band (分栏) mode: divide page into horizontal bands \int_new:N \l__luatexcn_content_n_bands_int \tl_new:N \l__luatexcn_content_band_gap_tl \tl_new:N \l__luatexcn_content_band_mode_tl \tl_new:N \l__luatexcn_content_band_heights_tl \tl_new:N \l__luatexcn_content_band_columns_tl % Granular border control (empty = inherit from border) \tl_new:N \l__luatexcn_content_column_border_tl \tl_new:N \l__luatexcn_content_band_border_tl % Content-level indent defaults (pushed to style stack in BodyText) \tl_new:N \l__luatexcn_content_indent_tl \tl_new:N \l__luatexcn_content_first_indent_tl \bool_new:N \l__luatexcn_content_has_indent_bool % Flag: currently inside BodyText environment (used by \chapter to decide text output) \bool_new:N \g__luatexcn_in_bodytext_bool \keys_define:nn { luatexcn / content } { border .bool_set:N = \l__luatexcn_content_border_bool, border .initial:n = false, border-thickness .tl_set:N = \l__luatexcn_content_border_thickness_tl, border-thickness .initial:n = {0.4pt}, padding-top .tl_set:N = \l__luatexcn_content_padding_top_tl, padding-top .initial:n = {0pt}, padding-bottom .tl_set:N = \l__luatexcn_content_padding_bottom_tl, padding-bottom .initial:n = {0pt}, % Legacy aliases column-padding-top .tl_set:N = \l__luatexcn_content_padding_top_tl, column-padding-bottom .tl_set:N = \l__luatexcn_content_padding_bottom_tl, outer-border .bool_set:N = \l__luatexcn_content_outer_border_bool, outer-border .initial:n = false, outer-border-thickness .tl_set:N = \l__luatexcn_content_outer_border_thickness_tl, outer-border-thickness .initial:n = {1pt}, outer-border-sep .tl_set:N = \l__luatexcn_content_outer_border_sep_tl, outer-border-sep .initial:n = {2pt}, border-color .tl_set:N = \l__luatexcn_content_border_color_tl, border-color .initial:n = {black}, column-border .tl_set:N = \l__luatexcn_content_column_border_tl, column-border .initial:n = {}, band-border .tl_set:N = \l__luatexcn_content_band_border_tl, band-border .initial:n = {}, % Number of characters per column (used for auto-calculating grid-height) n-char-per-col .int_set:N = \l__luatexcn_content_n_char_per_col_int, n-char-per-col .initial:n = 0, % Grid layout settings grid-width .tl_set:N = \l__luatexcn_content_grid_width_tl, grid-width .initial:n = {1.5em}, grid-height .tl_set:N = \l__luatexcn_content_grid_height_tl, grid-height .initial:n = {1.2em}, vertical-align .tl_set:N = \l__luatexcn_content_valign_tl, vertical-align .initial:n = {center}, font-size .tl_set:N = \l__luatexcn_content_font_size_tl, font-size .initial:n = {12pt}, font .tl_set:N = \l__luatexcn_content_font_name_tl, font .initial:n = {}, font-features .tl_set:N = \l__luatexcn_content_font_features_tl, font-features .initial:n = {}, font-color .tl_set:N = \l__luatexcn_content_font_color_tl, font-color .initial:n = {}, n-column .int_set:N = \l__luatexcn_content_n_column_int, n-column .initial:n = 8, page-columns .tl_set:N = \l__luatexcn_content_page_columns_tl, page-columns .initial:n = {}, height .tl_set:N = \l__luatexcn_content_height_tl, height .initial:n = , spacing-col .tl_set:N = \l__luatexcn_content_col_spacing_tl, spacing-col .initial:n = {}, line-spacing .tl_set:N = \l__luatexcn_content_line_spacing_tl, line-spacing .initial:n = { \baselineskip }, banxin-ratio .tl_set:N = \l__luatexcn_content_banxin_ratio_tl, banxin-ratio .initial:n = {0.7}, layout-mode .tl_set:N = \l__luatexcn_content_layout_mode_tl, layout-mode .initial:n = {grid}, ignore-spaces .bool_set:N = \l__luatexcn_content_ignore_spaces_bool, ignore-spaces .initial:n = {true}, inter-cell-gap .tl_set:N = \l__luatexcn_content_inter_cell_gap_tl, inter-cell-gap .initial:n = {0pt}, cell-height .tl_set:N = \l__luatexcn_content_cell_height_tl, cell-height .initial:n = {}, cell-width .tl_set:N = \l__luatexcn_content_cell_width_tl, cell-width .initial:n = {}, cell-gap .tl_set:N = \l__luatexcn_content_cell_gap_tl, cell-gap .initial:n = {}, % 灵活行宽和间距参数(Free Mode) auto-column-width .bool_set:N = \l__luatexcn_content_auto_col_width_bool, auto-column-width .initial:n = false, column-spacing-top .tl_set:N = \l__luatexcn_content_col_spacing_top_tl, column-spacing-top .initial:n = {0pt}, column-spacing-bottom .tl_set:N = \l__luatexcn_content_col_spacing_bottom_tl, column-spacing-bottom .initial:n = {0pt}, column-spacing .meta:n = { column-spacing-top = #1, column-spacing-bottom = #1 }, paragraph-spacing .tl_set:N = \l__luatexcn_content_para_spacing_tl, paragraph-spacing .initial:n = {0pt}, % Digital mode: when true, BodyText uses async vbox + obeylines % (each source newline = one column break in PDF output) digital-mode .bool_set:N = \l__luatexcn_content_digital_mode_bool, digital-mode .initial:n = false, % Band (分栏) settings: divide page into horizontal bands % Each band contains vertical columns flowing right-to-left n-bands .int_set:N = \l__luatexcn_content_n_bands_int, n-bands .initial:n = 1, band-gap .tl_set:N = \l__luatexcn_content_band_gap_tl, band-gap .initial:n = {0pt}, % Band mode: "auto" = text flows across pages, "per-page" = each page independent band-mode .tl_set:N = \l__luatexcn_content_band_mode_tl, band-mode .initial:n = {auto}, % Manual band heights (comma-separated dim list, optional) band-heights .tl_set:N = \l__luatexcn_content_band_heights_tl, band-heights .initial:n = {}, % Columns per band (optional, defaults to page-columns) band-columns .tl_set:N = \l__luatexcn_content_band_columns_tl, band-columns .initial:n = {}, % Content-level indent defaults (pushed to style stack in BodyText begin) % first-indent: indent for first column of each paragraph (after \par or blank line) % indent: indent for non-first columns indent .tl_set:N = \l__luatexcn_content_indent_tl, indent .initial:n = {}, first-indent .tl_set:N = \l__luatexcn_content_first_indent_tl, first-indent .initial:n = {}, unknown .code:n = { \keys_set:nx { luatexcn / page } { \l_keys_key_tl = { #1 } } }, } % Internal: Sync content parameters to Lua (idempotent, can be called multiple times) \cs_new_protected:Npn \__luatexcn_content_sync_to_lua: { % Convert colors to normalized RGB strings for Lua \__luatexcn_color_to_rgb:NN \l__luatexcn_content_border_color_tl \l__luatexcn_content_border_rgb_tl \__luatexcn_color_to_rgb_or_nil:NN \l__luatexcn_page_background_color_tl \l__luatexcn_content_background_rgb_tl \__luatexcn_color_to_rgb_or_nil:NN \l__luatexcn_content_font_color_tl \l__luatexcn_content_font_rgb_tl % Sync params to Lua (idempotent) \lua_now:e { require('core.luatex-cn-core-content').sync_params({ border_on~=~\bool_if:NTF~\l__luatexcn_content_border_bool~{true}~{false}, column_border~=~\tl_if_empty:NTF~\l__luatexcn_content_column_border_tl~{nil}~{\l__luatexcn_content_column_border_tl}, band_border~=~\tl_if_empty:NTF~\l__luatexcn_content_band_border_tl~{nil}~{\l__luatexcn_content_band_border_tl}, outer_border_on~=~\bool_if:NTF~\l__luatexcn_content_outer_border_bool~{true}~{false}, border_thickness~=~[=[\luaescapestring{\l__luatexcn_content_border_thickness_tl}]=], outer_border_thickness~=~[=[\luaescapestring{\l__luatexcn_content_outer_border_thickness_tl}]=], outer_border_sep~=~[=[\luaescapestring{\l__luatexcn_content_outer_border_sep_tl}]=], padding_top~=~[=[\luaescapestring{\l__luatexcn_content_padding_top_tl}]=], padding_bottom~=~[=[\luaescapestring{\l__luatexcn_content_padding_bottom_tl}]=], n_column~=~\int_use:N~\l__luatexcn_content_n_column_int, grid_width~=~[=[\luaescapestring{\l__luatexcn_content_grid_width_tl}]=], grid_height~=~[=[\luaescapestring{\l__luatexcn_content_grid_height_tl}]=], page_columns~=~[=[\luaescapestring{\l__luatexcn_content_page_columns_tl}]=], vertical_align~=~[=[\luaescapestring{\l__luatexcn_content_valign_tl}]=], border_color~=~[=[\l__luatexcn_content_border_rgb_tl]=], background_color~=~[=[\l__luatexcn_content_background_rgb_tl]=], font_color~=~[=[\l__luatexcn_content_font_rgb_tl]=], font_size~=~[=[\luaescapestring{\l__luatexcn_content_font_size_tl}]=], layout_mode~=~[=[\luaescapestring{\l__luatexcn_content_layout_mode_tl}]=], ignore_spaces~=~\bool_if:NTF~\l__luatexcn_content_ignore_spaces_bool~{true}{false}, inter_cell_gap~=~[=[\luaescapestring{\l__luatexcn_content_inter_cell_gap_tl}]=], cell_height~=~[=[\luaescapestring{\l__luatexcn_content_cell_height_tl}]=], cell_width~=~[=[\luaescapestring{\l__luatexcn_content_cell_width_tl}]=], cell_gap~=~[=[\luaescapestring{\l__luatexcn_content_cell_gap_tl}]=], n_bands~=~\int_use:N~\l__luatexcn_content_n_bands_int, band_gap~=~[=[\luaescapestring{\l__luatexcn_content_band_gap_tl}]=], band_mode~=~[=[\luaescapestring{\l__luatexcn_content_band_mode_tl}]=], band_heights~=~[=[\luaescapestring{\l__luatexcn_content_band_heights_tl}]=], band_columns~=~[=[\luaescapestring{\l__luatexcn_content_band_columns_tl}]=] }) } } \NewDocumentCommand{\contentSetup}{ m } { \keys_set:nn { luatexcn / content } { #1 } } % Internal function to call Lua for Grid Layout \cs_new_protected:Npn \__luatexcn_content_process_grid:n #1 { % Put content into a vbox to preserve structure (paragraphs) \vbox_set:Nn \l_tmpa_box { \dim_set_eq:NN \hsize \c_max_dim \dim_set_eq:NN \hfuzz \c_max_dim \int_set:Nn \hbadness { 10000 } \dim_zero:N \parindent #1 } \skip_zero:N \topskip % Final sync (catches any overrides from environment optional args) \__luatexcn_content_sync_to_lua: % Initialize content style (once per content block) \lua_now:n { require('core.luatex-cn-core-content').init_style() } % Process content \lua_now:e { core.process(\int_value:w \l_tmpa_box, { height~=~[=[\luaescapestring{\l__luatexcn_content_height_tl}]=], col_limit~=~\int_use:N~\l__luatexcn_content_n_char_per_col_int }) } } % Define Main Command with Key-Value interface % Syntax: \Content[key=val]{text} \NewDocumentCommand{\Content}{ O{} +m } { % \par first: \nointerlineskip assigns \prevdepth, which is only legal in % vertical mode. Preceding horizontal material (e.g. an earlier content % block) would otherwise make it raise "You can't use \prevdepth ...". \par \nointerlineskip \noindent \group_begin: \keys_set:nn { luatexcn / content } { #1 } % Apply page geometry \luatexcn_apply_geometry: % Auto-layout for guji-style (computes grid dimensions if needed) \__luatexcn_content_auto_layout: % Common setup (font, skips, height fallback) \__luatexcn_content_setup_font: \__luatexcn_content_zero_skips: \__luatexcn_content_calc_height: % Apply background color if set \__luatexcn_content_apply_background: % Override \clearpage/\newpage for grid processing (scoped to this group) \__luatexcn_page_override_pagebreak: \__luatexcn_content_process_grid:n { #2 } \group_end: } % BodyText environment — uses async vbox capture (\vbox_set:Nw) so that % digital-mode obeylines can take effect during token reading. % When digital-mode is false, behaves identically to the old sync approach. \NewDocumentEnvironment{BodyText}{ O{} } { % \par first: see the note in \Content — \nointerlineskip is a \prevdepth % assignment and is only legal in vertical mode. \par \nointerlineskip \noindent \group_begin: % Mark that we are inside BodyText (used by \chapter for text output) \bool_gset_true:N \g__luatexcn_in_bodytext_bool \keys_set:nn { luatexcn / content } { #1 } % Apply page geometry \luatexcn_apply_geometry: % Auto-layout for guji-style (computes grid dimensions if needed) \__luatexcn_content_auto_layout: % Common setup (font, skips, height fallback) \__luatexcn_content_setup_font: \__luatexcn_content_zero_skips: \__luatexcn_content_calc_height: % Apply background color if set \__luatexcn_content_apply_background: % Override \clearpage/\newpage for grid processing (scoped to this group) \__luatexcn_page_override_pagebreak: % Content-level indent defaults: push to style stack if configured \bool_set_false:N \l__luatexcn_content_has_indent_bool \bool_lazy_or:nnT { ! \tl_if_empty_p:N \l__luatexcn_content_indent_tl } { ! \tl_if_empty_p:N \l__luatexcn_content_first_indent_tl } { \__luatexcn_style_init_and_parse:n {} \tl_if_empty:NTF \l__luatexcn_content_indent_tl { \tl_set:Nn \l__luatexcn_style_indent_tl { 0 } } { \tl_set_eq:NN \l__luatexcn_style_indent_tl \l__luatexcn_content_indent_tl } \tl_if_empty:NF \l__luatexcn_content_first_indent_tl { \tl_set_eq:NN \l__luatexcn_style_first_indent_tl \l__luatexcn_content_first_indent_tl } \__luatexcn_style_push_and_set: \bool_set_true:N \l__luatexcn_content_has_indent_bool } % Begin async vbox capture \vbox_set:Nw \l_tmpa_box \dim_set_eq:NN \hsize \c_max_dim \dim_set_eq:NN \hfuzz \c_max_dim \int_set:Nn \hbadness { 10000 } \dim_zero:N \parindent % Global \\ hook: restore temporary indent (\缩进[N]) on line break. % Safe to call unconditionally — \luatexcnRestoreTempIndent is a no-op % when no temporary indent is active (guarded by bool). \cs_set_eq:NN \__luatexcn_original_newline_body: \\ \cs_set:Npn \\ { \luatexcnRestoreTempIndent \__luatexcn_original_newline_body: } % Paragraph detection: increment block_id at each TeX paragraph start % so layout engine applies first-indent at paragraph boundaries % (\par or blank lines trigger new TeX paragraphs → \everypar fires) \everypar{ \int_gincr:N \g_luatexcn_paragraph_block_id_int \setluatexattribute\cnverticalblockid { \int_use:N \g_luatexcn_paragraph_block_id_int } } % Digital mode: activate obeylines for newline-as-column-break % Semantic mode: suppress newline spaces via Lua callback \bool_if:NTF \l__luatexcn_content_digital_mode_bool { \luatexcndigitalobeylines } { \lua_now:n { require('core.luatex-cn-core-content').enable_suppress_newline() } } } { \vbox_set_end: \skip_zero:N \topskip % Disable newline space suppression (if enabled in begin) \bool_if:NF \l__luatexcn_content_digital_mode_bool { \lua_now:n { require('core.luatex-cn-core-content').disable_suppress_newline() } } % Final sync (catches any overrides from environment optional args) \__luatexcn_content_sync_to_lua: % Initialize content style (once per content block) \lua_now:n { require('core.luatex-cn-core-content').init_style() } % Pre-render banxin sections into boxes (before main pipeline) \cs_if_exist:NT \__luatexcn_banxin_typeset_sections: { \__luatexcn_banxin_typeset_sections: } % Process content through three-stage pipeline \lua_now:e { core.process( \int_value:w \l_tmpa_box , { height~=~[=[\luaescapestring{\l__luatexcn_content_height_tl}]=], col_limit~=~\int_use:N~\l__luatexcn_content_n_char_per_col_int }) } % Pop content-level indent from style stack (if pushed in begin) \bool_if:NT \l__luatexcn_content_has_indent_bool { \__luatexcn_style_pop_with_indent_reset: } % Mark that we left BodyText \bool_gset_false:N \g__luatexcn_in_bodytext_bool \group_end: } % Space Command - inserts ideographic space characters (U+3000) for vertical layout % Syntax: \Space or \Space[n] (n = number of grid cells, default 1) \NewDocumentCommand{\Space}{ O{1} } { \prg_replicate:nn { #1 } { \char"3000\relax } \ignorespaces } % MissingChar Command - inserts a white square (U+25A1 □) as placeholder for missing/illegible characters % Syntax: \MissingChar or \MissingChar[n] (n = number of missing characters, default 1) \NewDocumentCommand{\MissingChar}{ O{1} } { \prg_replicate:nn { #1 } { \char"25A1\relax } \ignorespaces } % Sideways Command (clreq 直排中西混排配置之「顺时针旋转 90°」) % Syntax: \Sideways{English} / \横置{English} % 组内字形经 LuaTeX 属性标记(组结束自动恢复),引擎三处协同:字幅 = % advance 宽度、串内字距归零、渲染绕共同基线旋转 90°。适合较长的西文 % 词句;单个字母/缩略语通常直立即可(引擎默认逐字入格)。 \NewDocumentCommand{\Sideways}{ m } { \group_begin: \lua_now:e { local~c~=~require('core.luatex-cn-constants') tex.setattribute(c.ATTR_SIDEWAYS,~1) } #1 \group_end: } % CombineUpright Command (clreq 直排中西混排配置之「横排入一个字格」, % 即 CSS text-combine-upright / 日文縦中横) % Syntax: \CombineUpright{12} / \中横排{12} % 组内字形共占一个字幅:layout 让整组只占组首一格,render 把组员横排进 % 这一格,总宽超过 1em 时只做横向压缩。适合两三位数字/极短西文(日期 % 「\中横排{12}月」、卷次等);较长的词句用 \横置。组号递增,相邻两组 % 不会误并为一格。 \int_new:N \g__luatexcn_content_tcy_int \NewDocumentCommand{\CombineUpright}{ m } { \group_begin: \int_gincr:N \g__luatexcn_content_tcy_int \lua_now:e { local~c~=~require('core.luatex-cn-constants') tex.setattribute(c.ATTR_TCY,~\int_use:N \g__luatexcn_content_tcy_int) } #1 \group_end: } % ============================================================================ % Shared Helper Functions for Content Environments % ============================================================================ % Helper: Setup font if font is specified % Uses RawFeature={+vert,+vrt2} as default for CJK vertical typesetting \cs_new_protected:Npn \__luatexcn_content_setup_font: { \tl_if_empty:NF \l__luatexcn_content_font_name_tl { \tl_if_empty:NTF \l__luatexcn_content_font_features_tl { \exp_args:NV \setmainfont \l__luatexcn_content_font_name_tl [RawFeature={+vert,+vrt2}] } { \exp_args:NV \setmainfont \l__luatexcn_content_font_name_tl [\l__luatexcn_content_font_features_tl] } } \fontsize{\l__luatexcn_content_font_size_tl}{\l__luatexcn_content_line_spacing_tl}\selectfont } % Helper: Zero out vertical skips for exact grid layout \cs_new_protected:Npn \__luatexcn_content_zero_skips: { \dim_zero:N \topskip \dim_zero:N \parskip \dim_zero:N \partopsep \dim_zero:N \topsep \dim_zero:N \itemsep \dim_zero:N \parsep \dim_zero:N \baselineskip \dim_zero:N \lineskip \dim_zero:N \lineskiplimit \dim_zero:N \parindent } % Helper: Calculate height from page dimensions if not set \cs_new_protected:Npn \__luatexcn_content_calc_height: { \tl_if_empty:NT \l__luatexcn_content_height_tl { \tl_set:Nx \l__luatexcn_content_height_tl { \dim_eval:n { \l__luatexcn_page_paper_height_tl - \l__luatexcn_page_margin_top_tl - \l__luatexcn_page_margin_bottom_tl } } } } % Helper: Apply background color if specified \cs_new_protected:Npn \__luatexcn_content_apply_background: { \tl_if_empty:NF \l__luatexcn_page_background_color_tl { \tl_if_in:NnTF \l__luatexcn_page_background_color_tl { , } { \pagecolor [RGB] { \l__luatexcn_page_background_color_tl } } { \pagecolor { \l__luatexcn_page_background_color_tl } } } } % Helper: Auto-layout calculation for guji-style content % Calls Lua to compute grid dimensions based on page and content parameters % Only runs if n-column > 0 (guji-style with banxin) \cs_new_protected:Npn \__luatexcn_content_auto_layout: { \int_compare:nNnT { \l__luatexcn_content_n_column_int } > { 0 } { \lua_now:e { require('core.luatex-cn-core-content').guji_auto_layout({ border_on~=~\bool_if:NTF~\l__luatexcn_content_border_bool~{true}~{false}, outer_border_on~=~\bool_if:NTF~\l__luatexcn_content_outer_border_bool~{true}~{false}, border_thickness~=~"\l__luatexcn_content_border_thickness_tl", outer_border_thickness~=~"\l__luatexcn_content_outer_border_thickness_tl", outer_border_sep~=~"\l__luatexcn_content_outer_border_sep_tl", padding_top~=~"\l__luatexcn_content_padding_top_tl", padding_bottom~=~"\l__luatexcn_content_padding_bottom_tl", n_column~=~\int_use:N~\l__luatexcn_content_n_column_int, n_char_per_col~=~\int_use:N~\l__luatexcn_content_n_char_per_col_int, grid_height~=~"\l__luatexcn_content_grid_height_tl", banxin_ratio~=~"\l__luatexcn_content_banxin_ratio_tl", }) } } } \ExplSyntaxOff % Digital obeylines helper: make ^^M (newline) produce column breaks. \def\luatexcndigitalobeylines{% \obeylines \begingroup\lccode`\~=`\^^M \lowercase{\endgroup\def~{\luatexcnRestoreTempIndent\leavevmode\par\penalty-10005\relax}}% } % ============================================================ % Chinese aliases / 中文别名 % ============================================================ % Simplified Chinese / 简体 \NewCommandCopy{\空格}{\Space} \NewCommandCopy{\缺字}{\MissingChar} \NewCommandCopy{\横置}{\Sideways} \NewCommandCopy{\橫置}{\Sideways} \NewCommandCopy{\中横排}{\CombineUpright} \NewCommandCopy{\中橫排}{\CombineUpright} \NewCommandCopy{\内容设置}{\contentSetup} \NewCommandCopy{\内容}{\Content} \NewEnvironmentCopy{正文}{BodyText} % Traditional Chinese / 繁体 \NewCommandCopy{\內容設置}{\contentSetup} \NewCommandCopy{\內容}{\Content} % ============================================================ % Chinese key aliases / 中文 Key 别名 % ============================================================ \ExplSyntaxOn \keys_define:nn { luatexcn / content } { % 简体 边框 .bool_set:N = \l__luatexcn_content_border_bool, 边框粗细 .tl_set:N = \l__luatexcn_content_border_thickness_tl, 上填充 .tl_set:N = \l__luatexcn_content_padding_top_tl, 下填充 .tl_set:N = \l__luatexcn_content_padding_bottom_tl, % Legacy aliases 栏上填充 .tl_set:N = \l__luatexcn_content_padding_top_tl, 栏下填充 .tl_set:N = \l__luatexcn_content_padding_bottom_tl, 外边框 .bool_set:N = \l__luatexcn_content_outer_border_bool, 外边框粗细 .tl_set:N = \l__luatexcn_content_outer_border_thickness_tl, 外边框间距 .tl_set:N = \l__luatexcn_content_outer_border_sep_tl, 边框色 .tl_set:N = \l__luatexcn_content_border_color_tl, 行边框 .tl_set:N = \l__luatexcn_content_column_border_tl, 栏边框 .tl_set:N = \l__luatexcn_content_band_border_tl, 每行字数 .int_set:N = \l__luatexcn_content_n_char_per_col_int, 格宽 .tl_set:N = \l__luatexcn_content_grid_width_tl, 格高 .tl_set:N = \l__luatexcn_content_grid_height_tl, 纵对齐 .tl_set:N = \l__luatexcn_content_valign_tl, 字号 .tl_set:N = \l__luatexcn_content_font_size_tl, 字体 .tl_set:N = \l__luatexcn_content_font_name_tl, 字体特性 .tl_set:N = \l__luatexcn_content_font_features_tl, 字体颜色 .tl_set:N = \l__luatexcn_content_font_color_tl, 行数 .int_set:N = \l__luatexcn_content_n_column_int, 版面行数 .tl_set:N = \l__luatexcn_content_page_columns_tl, 高度 .tl_set:N = \l__luatexcn_content_height_tl, 行间距 .tl_set:N = \l__luatexcn_content_col_spacing_tl, 行距 .tl_set:N = \l__luatexcn_content_line_spacing_tl, 版心比例 .tl_set:N = \l__luatexcn_content_banxin_ratio_tl, 布局模式 .tl_set:N = \l__luatexcn_content_layout_mode_tl, 忽略空格 .bool_set:N = \l__luatexcn_content_ignore_spaces_bool, 字间隙 .tl_set:N = \l__luatexcn_content_inter_cell_gap_tl, 字格高 .tl_set:N = \l__luatexcn_content_cell_height_tl, 字格宽 .tl_set:N = \l__luatexcn_content_cell_width_tl, 字格间距 .tl_set:N = \l__luatexcn_content_cell_gap_tl, 自动行宽 .bool_set:N = \l__luatexcn_content_auto_col_width_bool, 行上间距 .tl_set:N = \l__luatexcn_content_col_spacing_top_tl, 行下间距 .tl_set:N = \l__luatexcn_content_col_spacing_bottom_tl, 行间距 .meta:n = { column-spacing-top = #1, column-spacing-bottom = #1 }, 段间距 .tl_set:N = \l__luatexcn_content_para_spacing_tl, 数字模式 .bool_set:N = \l__luatexcn_content_digital_mode_bool, 缩进 .tl_set:N = \l__luatexcn_content_indent_tl, 首行缩进 .tl_set:N = \l__luatexcn_content_first_indent_tl, 分栏 .int_set:N = \l__luatexcn_content_n_bands_int, 栏间距 .tl_set:N = \l__luatexcn_content_band_gap_tl, 栏模式 .tl_set:N = \l__luatexcn_content_band_mode_tl, 栏高度 .tl_set:N = \l__luatexcn_content_band_heights_tl, 栏行数 .tl_set:N = \l__luatexcn_content_band_columns_tl, % 繁体(与简体不同形的) 邊框 .bool_set:N = \l__luatexcn_content_border_bool, 邊框粗細 .tl_set:N = \l__luatexcn_content_border_thickness_tl, 外邊框 .bool_set:N = \l__luatexcn_content_outer_border_bool, 外邊框粗細 .tl_set:N = \l__luatexcn_content_outer_border_thickness_tl, 外邊框間距 .tl_set:N = \l__luatexcn_content_outer_border_sep_tl, 邊框色 .tl_set:N = \l__luatexcn_content_border_color_tl, 行邊框 .tl_set:N = \l__luatexcn_content_column_border_tl, 欄邊框 .tl_set:N = \l__luatexcn_content_band_border_tl, 每行字數 .int_set:N = \l__luatexcn_content_n_char_per_col_int, 格寬 .tl_set:N = \l__luatexcn_content_grid_width_tl, 縱對齊 .tl_set:N = \l__luatexcn_content_valign_tl, 字號 .tl_set:N = \l__luatexcn_content_font_size_tl, 字體 .tl_set:N = \l__luatexcn_content_font_name_tl, 字體特性 .tl_set:N = \l__luatexcn_content_font_features_tl, 字體顏色 .tl_set:N = \l__luatexcn_content_font_color_tl, 行數 .int_set:N = \l__luatexcn_content_n_column_int, 版面行數 .tl_set:N = \l__luatexcn_content_page_columns_tl, 行間距 .tl_set:N = \l__luatexcn_content_col_spacing_tl, 佈局模式 .tl_set:N = \l__luatexcn_content_layout_mode_tl, 字間隙 .tl_set:N = \l__luatexcn_content_inter_cell_gap_tl, 字格寬 .tl_set:N = \l__luatexcn_content_cell_width_tl, 字格間距 .tl_set:N = \l__luatexcn_content_cell_gap_tl, 自動行寬 .bool_set:N = \l__luatexcn_content_auto_col_width_bool, 行上間距 .tl_set:N = \l__luatexcn_content_col_spacing_top_tl, 行下間距 .tl_set:N = \l__luatexcn_content_col_spacing_bottom_tl, 行間距 .meta:n = { column-spacing-top = #1, column-spacing-bottom = #1 }, 段間距 .tl_set:N = \l__luatexcn_content_para_spacing_tl, 數字模式 .bool_set:N = \l__luatexcn_content_digital_mode_bool, 縮進 .tl_set:N = \l__luatexcn_content_indent_tl, 首行縮進 .tl_set:N = \l__luatexcn_content_first_indent_tl, 分欄 .int_set:N = \l__luatexcn_content_n_bands_int, 欄間距 .tl_set:N = \l__luatexcn_content_band_gap_tl, 欄模式 .tl_set:N = \l__luatexcn_content_band_mode_tl, 欄高度 .tl_set:N = \l__luatexcn_content_band_heights_tl, 欄行數 .tl_set:N = \l__luatexcn_content_band_columns_tl, } \ExplSyntaxOff % Band break command / 换栏命令 \NewDocumentCommand{\换栏}{}{% \directlua{require('core.luatex-cn-core-table').reset_band_cells()}% \penalty -10006\relax } \NewDocumentCommand{\換欄}{}{% \directlua{require('core.luatex-cn-core-table').reset_band_cells()}% \penalty -10006\relax } % Half-page break command / 换半页命令 % Fills remaining columns of the current half-page and advances to the next half-page. \NewDocumentCommand{\NewHalfPage}{}{\penalty -10010\relax} \NewDocumentCommand{\换半页}{}{\penalty -10010\relax} \NewDocumentCommand{\換半頁}{}{\penalty -10010\relax} % ============================================================ % IllustrationPage: suppress column borders (丝栏) for content within this scope. % The outer border is not affected. % Usage: % \插图页 — command form: applies to all content until next \clearpage % \恢复丝栏 — restore column borders (call after \clearpage) % ============================================================ \ExplSyntaxOn \bool_new:N \g__luatexcn_illustration_page_bool \cs_new_protected:Npn \__luatexcn_illustration_page_push: { % Push column_border=false style BEFORE the penalty so the penalty % node carries the new style attribute (render-page scans it). \lua_now:n { local~sr~=~require('util.luatex-cn-style-registry') sr.push({~column_border~=~false~}) } \edef \l__luatexcn_tmpa_tl { \lua_now:n { local~sr~=~require('util.luatex-cn-style-registry') tex.print(tostring(sr.current_id())) } } \setluatexattribute \cnverticalstyle { \l__luatexcn_tmpa_tl } \penalty -10010\relax \bool_gset_true:N \g__luatexcn_illustration_page_bool } \cs_new_protected:Npn \__luatexcn_illustration_page_pop: { \bool_if:NT \g__luatexcn_illustration_page_bool { \edef \l__luatexcn_tmpa_tl { \lua_now:n { local~sr~=~require('util.luatex-cn-style-registry') sr.pop() tex.print(tostring(sr.current_id()~or~0)) } } \setluatexattribute \cnverticalstyle { \l__luatexcn_tmpa_tl } \bool_gset_false:N \g__luatexcn_illustration_page_bool } } \NewDocumentCommand{\IllustrationPage}{} { \__luatexcn_illustration_page_push: } \NewDocumentCommand{\EndIllustrationPage}{} { \__luatexcn_illustration_page_pop: } \ExplSyntaxOff \NewCommandCopy{\插图页}{\IllustrationPage} \NewCommandCopy{\插圖頁}{\IllustrationPage} \NewCommandCopy{\恢复丝栏}{\EndIllustrationPage} \NewCommandCopy{\恢復絲欄}{\EndIllustrationPage} \endinput