% 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-textbox.sty
% TextBox support for vertical typesetting
% This is a subpackage of luatex_cn
%
% NOTE: This file must be loaded AFTER luatexcn/vertical keys are defined
%
\RequirePackage{core/luatex-cn-core-base}
\RequirePackage{expl3}
\RequirePackage{xparse}
\ProvidesExplPackage {core/luatex-cn-core-textbox} {2026/08/07} {0.4.1} {TextBox Support for Vertical Typesetting}

% Grid Textbox Command (Simplified for Single Column)
% Syntax: \GridTextbox[height=N, fill=true/false]{content}
% height is in grid units (integers), width is always 1
% Key-value interface for \GridTextbox
\bool_new:N \l__luatexcn_textbox_border_bool
\tl_new:N \l__luatexcn_textbox_box_align_tl
\tl_new:N \l__luatexcn_textbox_inner_gw_tl
\tl_new:N \l__luatexcn_textbox_inner_gh_tl
\dim_new:N \l__luatexcn_textbox_inner_gw_dim
\dim_new:N \l__luatexcn_textbox_inner_gh_dim
\bool_new:N \l__luatexcn_textbox_floating_bool
\bool_new:N \l__luatexcn_textbox_transparent_bool
\tl_new:N \l__luatexcn_textbox_x_tl
\tl_new:N \l__luatexcn_textbox_y_tl
\tl_new:N \l__luatexcn_textbox_bg_color_tl
\tl_new:N \l__luatexcn_textbox_font_color_tl
\tl_new:N \l__luatexcn_textbox_bg_rgb_tl
\tl_new:N \l__luatexcn_textbox_font_rgb_tl
\tl_new:N \l__luatexcn_textbox_font_size_tl
\tl_new:N \l__luatexcn_textbox_floating_paper_width_tl
\tl_new:N \l__luatexcn_textbox_border_shape_tl
\tl_new:N \l__luatexcn_textbox_border_color_tl
\tl_new:N \l__luatexcn_textbox_border_width_tl
\tl_new:N \l__luatexcn_textbox_border_margin_tl
\tl_new:N \l__luatexcn_textbox_border_margin_x_tl
\tl_new:N \l__luatexcn_textbox_border_margin_y_tl
\tl_new:N \l__luatexcn_textbox_outer_margin_tl
\tl_new:N \l__luatexcn_textbox_border_rgb_tl
\bool_new:N \l__luatexcn_textbox_outer_border_bool
\tl_new:N \l__luatexcn_textbox_outer_border_thickness_tl
\tl_new:N \l__luatexcn_textbox_outer_border_sep_tl
\tl_new:N \l__luatexcn_textbox_padding_top_tl
\tl_new:N \l__luatexcn_textbox_padding_bottom_tl

\keys_define:nn { luatexcn / textbox }
  {
    height .tl_set:N = \l__luatexcn_textbox_height_tl,
    height .initial:n = 0,
    n-cols .int_set:N = \l__luatexcn_textbox_n_cols_int,
    n-cols .initial:n = 0,
    outer-cols .int_set:N = \l__luatexcn_textbox_outer_cols_int,
    outer-cols .initial:n = 0,
    fill .meta:n = { box-align = fill },
    box-align .tl_set:N = \l__luatexcn_textbox_box_align_tl,
    box-align .initial:n = {top},
    border .bool_set:N = \l__luatexcn_textbox_border_bool,
    border .initial:n = false,
    banxin .bool_set:N = \l__luatexcn_textbox_banxin_bool,
    column-align .tl_set:N = \l__luatexcn_textbox_col_align_tl,
    column-align .initial:n = {},
    % debug key removed - use global \LtcDebugOn

    vertical-align .tl_set:N = \l__luatexcn_textbox_box_align_tl,
    floating .bool_set:N = \l__luatexcn_textbox_floating_bool,
    floating .initial:n = false,
    transparent .bool_set:N = \l__luatexcn_textbox_transparent_bool,
    transparent .initial:n = true,
    floating-paper-width .tl_set:N = \l__luatexcn_textbox_floating_paper_width_tl,
    floating-paper-width .initial:n = 0pt,
    x .tl_set:N = \l__luatexcn_textbox_x_tl,
    x .initial:n = 0pt,
    y .tl_set:N = \l__luatexcn_textbox_y_tl,
    y .initial:n = 0pt,
    background-color .tl_set:N = \l__luatexcn_textbox_bg_color_tl,
    background-color .initial:n = {},
    font-color .tl_set:N = \l__luatexcn_textbox_font_color_tl,
    font-color .initial:n = {},
    font-size .tl_set:N = \l__luatexcn_textbox_font_size_tl,
    font-size .initial:n = {},
    grid-width .tl_set:N = \l__luatexcn_textbox_inner_gw_tl,
    grid-width .initial:n = {},
    grid-height .tl_set:N = \l__luatexcn_textbox_inner_gh_tl,
    grid-height .initial:n = {},
    % Border shape parameters (rect/octagon/circle)
    border-shape .tl_set:N = \l__luatexcn_textbox_border_shape_tl,
    border-shape .initial:n = {none},
    border-color .tl_set:N = \l__luatexcn_textbox_border_color_tl,
    border-color .initial:n = {},
    border-width .tl_set:N = \l__luatexcn_textbox_border_width_tl,
    border-width .initial:n = {0.4pt},
    border-margin .tl_set:N = \l__luatexcn_textbox_border_margin_tl,
    border-margin .initial:n = {0pt},
    border-margin-x .tl_set:N = \l__luatexcn_textbox_border_margin_x_tl,
    border-margin-x .initial:n = {},
    border-margin-y .tl_set:N = \l__luatexcn_textbox_border_margin_y_tl,
    border-margin-y .initial:n = {},
    border-offset .tl_set:N = \l__luatexcn_textbox_border_offset_tl,
    border-offset .initial:n = {0pt},
    outer-margin .tl_set:N = \l__luatexcn_textbox_outer_margin_tl,
    outer-margin .initial:n = {0pt},
    outer-border .bool_set:N = \l__luatexcn_textbox_outer_border_bool,
    outer-border .initial:n = false,
    outer-border-thickness .tl_set:N = \l__luatexcn_textbox_outer_border_thickness_tl,
    outer-border-thickness .initial:n = {1pt},
    outer-border-sep .tl_set:N = \l__luatexcn_textbox_outer_border_sep_tl,
    outer-border-sep .initial:n = {2pt},
    padding-top .tl_set:N = \l__luatexcn_textbox_padding_top_tl,
    padding-top .initial:n = {},
    padding-bottom .tl_set:N = \l__luatexcn_textbox_padding_bottom_tl,
    padding-bottom .initial:n = {},
    % Legacy aliases
    column-padding-top .tl_set:N = \l__luatexcn_textbox_padding_top_tl,
    column-padding-bottom .tl_set:N = \l__luatexcn_textbox_padding_bottom_tl,
    fill-padding-top .tl_set:N = \l__luatexcn_textbox_padding_top_tl,
    fill-padding-bottom .tl_set:N = \l__luatexcn_textbox_padding_bottom_tl,
    % 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} } } }
              { }
          }
      },
  }

\NewDocumentCommand{\textboxSetup}{ m }
  {
    \keys_set:nn { luatexcn / textbox } { #1 }
  }

\NewDocumentCommand{\TextBox}{ O{} +m }
  {
    \group_begin:
    % Inherit debug setting from global environment
    % No local override anymore
    \keys_set:nn { luatexcn / textbox } { #1 }
    % Apply settings overrides (punct-mode etc.) via judouSetup within this group
    \tl_if_empty:NF \l__luatexcn_settings_punct_mode_tl
      { \judouSetup { punct-mode = \l__luatexcn_settings_punct_mode_tl } }
    % Width is always 1 for outer layout (textbox occupies 1 column externally)
    \setluatexattribute\cnverticaltextboxwidth{1}

    % Calculate inner grid width via Lua (replaces TeX-side decision tree)
    \dim_set:Nn \l__luatexcn_textbox_inner_gw_dim {
      \lua_now:e {
        local ~ tb = require('core.luatex-cn-core-textbox')
        tex.print(tostring(tb.calc_inner_grid_width({
          inner_gw =
            \tl_if_empty:NTF \l__luatexcn_textbox_inner_gw_tl
              { 0 }
              { \dim_to_decimal_in_sp:n { \l__luatexcn_textbox_inner_gw_tl } },
          column_width = \dim_to_decimal_in_sp:n { \g__luatexcn_column_current_width_dim },
          outer_cols = \int_use:N \l__luatexcn_textbox_outer_cols_int,
          n_cols = \int_use:N \l__luatexcn_textbox_n_cols_int,
          content_gw = \dim_to_decimal_in_sp:n { \l__luatexcn_content_grid_width_tl },
        })))
      } sp
    }
    \tl_if_empty:NTF \l__luatexcn_textbox_inner_gh_tl
      { \dim_set:Nn \l__luatexcn_textbox_inner_gh_dim { \l__luatexcn_content_grid_height_tl } }
      { \dim_set:Nn \l__luatexcn_textbox_inner_gh_dim { \l__luatexcn_textbox_inner_gh_tl } }

    % Capture content in a box and process it via Lua for inner verticality
    \vbox_set:Nn \l_tmpa_box {
      \setluatexattribute\cnverticaltextboxwidth{0}
      \setluatexattribute\cnverticaltextboxheight{0}
      % Reset jiazhu attributes inside textbox: inner content is not textflow,
      % even if textbox is inside \夹注{}. Without this reset, inner glyphs
      % inherit ATTR_JIAZHU=1 and get sent to textflow layout, causing them to
      % be placed in sub-columns (horizontally) instead of stacked vertically.
      \setluatexattribute\cnverticaljiazhu{0}
      \setluatexattribute\cnverticaljiazhusub{0}
      \setluatexattribute\cnverticaljiazhumode{0}
      % Note: indent is inherited from style stack (no longer reset here)
      % Reset paragraph spacing to prevent unwanted glues in grid layout
      \dim_set:Nn \l_tmpa_dim { \l__luatexcn_content_grid_height_tl }
      % Scale font size
      \tl_if_empty:NTF \l__luatexcn_textbox_font_size_tl
        {
          % Default auto-scaling for multi-column boxes
          \int_compare:nNnT \l__luatexcn_textbox_n_cols_int > 1
            {
              \dim_set:Nn \l_tmpa_dim { \l__luatexcn_textbox_inner_gw_dim * 85 / 100 }
              \fontsize{\l_tmpa_dim}{\l__luatexcn_textbox_inner_gw_dim}\selectfont
            }
        }
        {
          % User-specified font size
          \dim_set:Nn \l_tmpa_dim { \l__luatexcn_textbox_font_size_tl }
          \fontsize{\l_tmpa_dim}{\l_tmpa_dim}\selectfont
        }
    % Inner line length: always max — column wrapping is done by the grid
    % layout (col_limit = height), not by TeX's line breaker. Letting TeX
    % pre-break at height*grid_height splits Western words with hyphens and
    % misaligns columns (issue #50): TeX measures Latin letters at natural
    % width while the grid gives every glyph a full cell.
    \dim_set_eq:NN \hsize \c_max_dim
    \dim_set_eq:NN \hfuzz \c_max_dim
    \int_set:Nn \hbadness { 10000 }
    #2
    }

    % Expand variables to ensure they contain the color string, not a macro name
    % Use o-expansion to preserve spaces (standard x-expansion strips them in ExplSyntax)
    \tl_set:No \l__luatexcn_textbox_bg_color_tl { \l__luatexcn_textbox_bg_color_tl }
    \tl_set:No \l__luatexcn_textbox_font_color_tl { \l__luatexcn_textbox_font_color_tl }

    % Convert background color to RGB
    \cs_if_exist:cTF { color @ \l__luatexcn_textbox_bg_color_tl }
      {
        \exp_args:NV \extractcolorspec \l__luatexcn_textbox_bg_color_tl \l_tmpa_tl
        \exp_args:NNx \convertcolorspec \l_tmpa_tl { rgb } \l_tmpb_tl
        \tl_set:Nx \l__luatexcn_textbox_bg_rgb_tl { \l_tmpb_tl }
      }
      { \tl_set_eq:NN \l__luatexcn_textbox_bg_rgb_tl \l__luatexcn_textbox_bg_color_tl }
    \tl_replace_all:Nnn \l__luatexcn_textbox_bg_rgb_tl { , } { ~ }

    % Convert font color to RGB
    \cs_if_exist:cTF { color @ \l__luatexcn_textbox_font_color_tl }
      {
        \exp_args:NV \extractcolorspec \l__luatexcn_textbox_font_color_tl \l_tmpa_tl
        \exp_args:NNx \convertcolorspec \l_tmpa_tl { rgb } \l_tmpb_tl
        \tl_set:Nx \l__luatexcn_textbox_font_rgb_tl { \l_tmpb_tl }
      }
      { \tl_set_eq:NN \l__luatexcn_textbox_font_rgb_tl \l__luatexcn_textbox_font_color_tl }
    \tl_replace_all:Nnn \l__luatexcn_textbox_font_rgb_tl { , } { ~ }

    % Convert border color to RGB
    \tl_set:No \l__luatexcn_textbox_border_color_tl { \l__luatexcn_textbox_border_color_tl }
    \cs_if_exist:cTF { color @ \l__luatexcn_textbox_border_color_tl }
      {
        \exp_args:NV \extractcolorspec \l__luatexcn_textbox_border_color_tl \l_tmpa_tl
        \exp_args:NNx \convertcolorspec \l_tmpa_tl { rgb } \l_tmpb_tl
        \tl_set:Nx \l__luatexcn_textbox_border_rgb_tl { \l_tmpb_tl }
      }
      { \tl_set_eq:NN \l__luatexcn_textbox_border_rgb_tl \l__luatexcn_textbox_border_color_tl }
    \tl_replace_all:Nnn \l__luatexcn_textbox_border_rgb_tl { , } { ~ }

    % Setup textbox params in _G.textbox (parsed once, used in Lua)
    \lua_now:e {
      vertical_textbox.setup({
        column_aligns~=~[=[\luaescapestring{\tl_use:N~\l__luatexcn_textbox_col_align_tl}]=],
        floating~=~\bool_if:NTF~\l__luatexcn_textbox_floating_bool~{true}~{false},
        floating_x~=~[=[\luaescapestring{\l__luatexcn_textbox_x_tl}]=],
        floating_y~=~[=[\luaescapestring{\l__luatexcn_textbox_y_tl}]=],
        floating_paper_width~=~[=[\luaescapestring{\l__luatexcn_textbox_floating_paper_width_tl}]=],
        outer_grid_height~=~\dim_to_decimal_in_sp:n { \l__luatexcn_content_grid_height_tl }
      })
    }

    \lua_now:e {vertical_textbox.process_inner_box(\int_value:w~\l_tmpa_box,~{
      n_cols~=~\int_use:N~\l__luatexcn_textbox_n_cols_int,
      outer_cols~=~\int_use:N~\l__luatexcn_textbox_outer_cols_int,
      height~=~[=[\luaescapestring{\tl_use:N \l__luatexcn_textbox_height_tl}]=],
      grid_width~=~\dim_to_decimal_in_sp:n { \l__luatexcn_textbox_inner_gw_dim },
      grid_height~=~\dim_to_decimal_in_sp:n { \l__luatexcn_textbox_inner_gh_dim },
      box_align~=~"\luaescapestring{\tl_use:N~\l__luatexcn_textbox_box_align_tl}",
      border~=~"\bool_if:NTF~\l__luatexcn_textbox_border_bool~{true}{false}",
      background_color~=~[=[\l__luatexcn_textbox_bg_rgb_tl]=],
      font_color~=~[=[\l__luatexcn_textbox_font_rgb_tl]=],
      font_size~=~[=[\luaescapestring{\l__luatexcn_textbox_font_size_tl}]=],
      border_shape~=~"\luaescapestring{\tl_use:N~\l__luatexcn_textbox_border_shape_tl}",
      border_color~=~[=[\l__luatexcn_textbox_border_rgb_tl]=],
      border_width~=~[=[\luaescapestring{\l__luatexcn_textbox_border_width_tl}]=],
      border_margin~=~[=[\luaescapestring{\l__luatexcn_textbox_border_margin_tl}]=],
      border_margin_x~=~[=[\luaescapestring{\l__luatexcn_textbox_border_margin_x_tl}]=],
      border_margin_y~=~[=[\luaescapestring{\l__luatexcn_textbox_border_margin_y_tl}]=],
      border_offset~=~[=[\luaescapestring{\l__luatexcn_textbox_border_offset_tl}]=],
      outer_margin~=~[=[\luaescapestring{\l__luatexcn_textbox_outer_margin_tl}]=],
      outer_border~=~\bool_if:NTF~\l__luatexcn_textbox_outer_border_bool~{true}{false},
      outer_border_thickness~=~[=[\luaescapestring{\l__luatexcn_textbox_outer_border_thickness_tl}]=],
      outer_border_sep~=~[=[\luaescapestring{\l__luatexcn_textbox_outer_border_sep_tl}]=],
      padding_top~=~[=[\luaescapestring{\l__luatexcn_textbox_padding_top_tl}]=],
      padding_bottom~=~[=[\luaescapestring{\l__luatexcn_textbox_padding_bottom_tl}]=],
      transparent~=~\bool_if:NTF~\l__luatexcn_textbox_transparent_bool~{true}{false}
    }) }
    \setluatexattribute\cnverticaltextboxwidth{0}
    \setluatexattribute\cnverticaltextboxheight{0}
    \bool_if:NTF \l__luatexcn_textbox_floating_bool
      {
        \lua_now:e { vertical_textbox.register_floating_box(\int_value:w~\l_tmpa_box,~{
          x~=~[=[\luaescapestring{\l__luatexcn_textbox_x_tl}]=],
          y~=~[=[\luaescapestring{\l__luatexcn_textbox_y_tl}]=]
        }) }
      }
      {
        \mode_leave_vertical: \box_use:N \l_tmpa_box
      }
    \group_end:
  }

% 填充文本框 - Backwards compatible: accepts either integer (old syntax) or key-value options (new syntax)
\NewDocumentCommand{\FillTextBox}{ O{} +m }
  {%
    \tl_set:Nn \l_tmpa_tl { #1 }
    \regex_match:nnTF { ^[0-9]+$ } { #1 }
      { \TextBox[height=#1, box-align=fill]{#2} }  % Old syntax: just an integer height
      { \TextBox[#1, box-align=fill]{#2} }          % New syntax: key-value options
  }

% ============================================================
% Cell size resolution helpers (for border-shape commands)
% Priority: content cell-width/cell-height param > font size (1em)
% ============================================================
\cs_new:Nn \__luatexcn_textbox_resolve_cell_gw:N
  {
    % Inside textflow (jiazhu), use current font's 1em (small size)
    % instead of content cell_width (body text size)
    \int_compare:nNnTF { \the\cnverticaljiazhu } > { 0 }
      { \dim_set:Nn #1 { 1em } }
      {
        \tl_if_empty:NTF \l__luatexcn_content_cell_width_tl
          { \dim_set:Nn #1 { 1em } }
          { \dim_set:Nn #1 { \l__luatexcn_content_cell_width_tl } }
      }
  }
% Resolve inner grid-height for border-shape textbox commands.
% Use outer grid_height so the textbox's internal row pitch matches the
% surrounding text — this prevents content overflow into the next row
% (#96 follow-up: body \octagon{真珠} previously overlapped the next
% glyph because gh = gw * 1.2 = 33.6pt > outer 28.5pt). Falls back to
% 1.2em when no outer grid is set.
\cs_new:Nn \__luatexcn_textbox_resolve_cell_gh:N
  {
    \int_compare:nNnTF { \the\cnverticaljiazhu } > { 0 }
      {
        \tl_if_empty:NTF \l__luatexcn_content_grid_height_tl
          { \dim_set:Nn #1 { 1.2em } }
          { \dim_set:Nn #1 { \l__luatexcn_content_grid_height_tl } }
      }
      {
        \tl_if_empty:NTF \l__luatexcn_content_cell_height_tl
          {
            \tl_if_empty:NTF \l__luatexcn_content_grid_height_tl
              { \dim_set:Nn #1 { 1.2em } }
              { \dim_set:Nn #1 { \l__luatexcn_content_grid_height_tl } }
          }
          { \dim_set:Nn #1 { \l__luatexcn_content_cell_height_tl } }
      }
  }

% ============================================================
% Border shape shortcuts (using cell-based sizing)
%
% 修饰词组合命名体系:
%   反白     = 黑底白字（无边框）
%   墨围     = 矩形边框
%   八角墨围 = 八角形边框
%   反白     = 可叠加（墨围反白、八角墨围反白）
%   带圈     = 圆形边框（独立）
% ============================================================

% -- 反白：黑底白字，无边框 --
% 不强制 1.2x 高度（无边框，字格高度即可）
\NewDocumentCommand{\反白}{ O{} m }
  {
    \__luatexcn_textbox_resolve_cell_gw:N \l_tmpa_dim
    \__luatexcn_textbox_resolve_cell_gh:N \l_tmpb_dim
    \TextBox[
      background-color={0.2~0.2~0.2},
      font-color=white,
      grid-width=\l_tmpa_dim,
      grid-height=\l_tmpb_dim,
      #1
    ]{#2}
  }

% -- 墨围：矩形黑色边框 --
\NewDocumentCommand{\墨围}{ O{} m }
  {
    \__luatexcn_textbox_resolve_cell_gw:N \l_tmpa_dim
    \__luatexcn_textbox_resolve_cell_gh:N \l_tmpb_dim
    \TextBox[
      border-shape=rect,
      grid-width=\l_tmpa_dim,
      grid-height=\l_tmpb_dim,
      border-margin-x=0.05em,
      border-margin-y=0.1em,
      outer-margin=0.15em,
      #1
    ]{#2}
  }

% -- 墨围反白：矩形黑色边框 + 黑底白字 --
% border-offset 使黑色边框线画在背景之外（白色区域上可见）
\NewDocumentCommand{\墨围反白}{ O{} m }
  {
    \__luatexcn_textbox_resolve_cell_gw:N \l_tmpa_dim
    \__luatexcn_textbox_resolve_cell_gh:N \l_tmpb_dim
    \TextBox[
      background-color={0.2~0.2~0.2},
      font-color=white,
      border-shape=rect,
      grid-width=\l_tmpa_dim,
      grid-height=\l_tmpb_dim,
      border-margin-x=0.05em,
      border-margin-y=0.1em,
      border-offset=0.1em,
      outer-margin=0.15em,
      #1
    ]{#2}
  }

% -- 八角墨围：八角形黑色边框 --
\NewDocumentCommand{\八角墨围}{ O{} m }
  {
    \__luatexcn_textbox_resolve_cell_gw:N \l_tmpa_dim
    \__luatexcn_textbox_resolve_cell_gh:N \l_tmpb_dim
    \TextBox[
      border-shape=octagon,
      grid-width=\l_tmpa_dim,
      grid-height=\l_tmpb_dim,
      border-margin-x=0.05em,
      border-margin-y=0.1em,
      outer-margin=0.15em,
      #1
    ]{#2}
  }

% -- 八角墨围反白：八角形黑色边框 + 黑底白字 --
% border-offset 使黑色边框线画在背景之外（白色区域上可见）
\NewDocumentCommand{\八角墨围反白}{ O{} m }
  {
    \__luatexcn_textbox_resolve_cell_gw:N \l_tmpa_dim
    \__luatexcn_textbox_resolve_cell_gh:N \l_tmpb_dim
    \TextBox[
      background-color={0.2~0.2~0.2},
      font-color=white,
      border-shape=octagon,
      grid-width=\l_tmpa_dim,
      grid-height=\l_tmpb_dim,
      border-margin-x=0.05em,
      border-margin-y=0.1em,
      border-offset=0.1em,
      outer-margin=0.15em,
      #1
    ]{#2}
  }

% -- 带圈：圆形边框 --
% 不强制 1.2x 高度（圆形几何已有视觉空间）
\NewDocumentCommand{\带圈}{ O{} m }
  {
    \__luatexcn_textbox_resolve_cell_gw:N \l_tmpa_dim
    \__luatexcn_textbox_resolve_cell_gh:N \l_tmpb_dim
    \TextBox[
      border-shape=circle,
      grid-width=\l_tmpa_dim,
      grid-height=\l_tmpb_dim,
      #1
    ]{#2}
  }

\ExplSyntaxOff%

% ============================================================
% Aliases / 别名
% ============================================================
% Simplified Chinese / 简体
\NewCommandCopy{\文本框}{\TextBox}
\NewCommandCopy{\文本框设置}{\textboxSetup}
\NewCommandCopy{\填充文本框}{\FillTextBox}
\NewDocumentCommand{\悬浮文本框}{ O{} +m }{\TextBox[floating=true, #1]{#2}}
\NewDocumentCommand{\FloatingTextBox}{ O{} +m }{\TextBox[floating=true, #1]{#2}}
% English
\NewCommandCopy{\inverted}{\反白}
\NewCommandCopy{\bordered}{\墨围}
\NewCommandCopy{\borderedInverted}{\墨围反白}
\NewCommandCopy{\octagon}{\八角墨围}
\NewCommandCopy{\octagonBordered}{\八角墨围}
\NewCommandCopy{\octagonBorderedInverted}{\八角墨围反白}
\NewCommandCopy{\circled}{\带圈}
% Traditional Chinese / 繁体
\NewCommandCopy{\文本框設置}{\textboxSetup}
\NewCommandCopy{\帶圈}{\带圈}
\NewCommandCopy{\墨圍}{\墨围}
\NewCommandCopy{\墨圍反白}{\墨围反白}
\NewCommandCopy{\八角墨圍}{\八角墨围}
\NewCommandCopy{\八角墨圍反白}{\八角墨围反白}

% ============================================================
% Chinese key aliases / 中文 Key 别名
% ============================================================
\ExplSyntaxOn
\keys_define:nn { luatexcn / textbox }
  {
    % 简体
    高度 .tl_set:N = \l__luatexcn_textbox_height_tl,
    列数 .int_set:N = \l__luatexcn_textbox_n_cols_int,
    外列数 .int_set:N = \l__luatexcn_textbox_outer_cols_int,
    填充 .meta:n = { box-align = fill },
    框对齐 .tl_set:N = \l__luatexcn_textbox_box_align_tl,
    边框 .bool_set:N = \l__luatexcn_textbox_border_bool,
    版心 .bool_set:N = \l__luatexcn_textbox_banxin_bool,
    列对齐 .meta:n = { column-align = #1 },
    纵对齐 .meta:n = { vertical-align = #1 },
    透明 .bool_set:N = \l__luatexcn_textbox_transparent_bool,
    悬浮 .bool_set:N = \l__luatexcn_textbox_floating_bool,
    悬浮纸宽 .tl_set:N = \l__luatexcn_textbox_floating_paper_width_tl,
    横坐标 .tl_set:N = \l__luatexcn_textbox_x_tl,
    纵坐标 .tl_set:N = \l__luatexcn_textbox_y_tl,
    底色 .tl_set:N = \l__luatexcn_textbox_bg_color_tl,
    字体颜色 .tl_set:N = \l__luatexcn_textbox_font_color_tl,
    字号 .tl_set:N = \l__luatexcn_textbox_font_size_tl,
    格宽 .meta:n = { grid-width = #1 },
    格高 .meta:n = { grid-height = #1 },
    边框形状 .tl_set:N = \l__luatexcn_textbox_border_shape_tl,
    边框色 .tl_set:N = \l__luatexcn_textbox_border_color_tl,
    边框宽 .tl_set:N = \l__luatexcn_textbox_border_width_tl,
    边框间距 .tl_set:N = \l__luatexcn_textbox_border_margin_tl,
    边框横距 .tl_set:N = \l__luatexcn_textbox_border_margin_x_tl,
    边框纵距 .tl_set:N = \l__luatexcn_textbox_border_margin_y_tl,
    外间距 .tl_set:N = \l__luatexcn_textbox_outer_margin_tl,
    外边框 .bool_set:N = \l__luatexcn_textbox_outer_border_bool,
    外边框粗细 .tl_set:N = \l__luatexcn_textbox_outer_border_thickness_tl,
    外边框间距 .tl_set:N = \l__luatexcn_textbox_outer_border_sep_tl,
    % 繁体（与简体不同形的）
    列數 .int_set:N = \l__luatexcn_textbox_n_cols_int,
    外列數 .int_set:N = \l__luatexcn_textbox_outer_cols_int,
    填充 .meta:n = { box-align = fill },
    框對齊 .tl_set:N = \l__luatexcn_textbox_box_align_tl,
    邊框 .bool_set:N = \l__luatexcn_textbox_border_bool,
    列對齊 .meta:n = { column-align = #1 },
    縱對齊 .meta:n = { vertical-align = #1 },
    透明 .bool_set:N = \l__luatexcn_textbox_transparent_bool,
    懸浮 .bool_set:N = \l__luatexcn_textbox_floating_bool,
    懸浮紙寬 .tl_set:N = \l__luatexcn_textbox_floating_paper_width_tl,
    橫坐標 .tl_set:N = \l__luatexcn_textbox_x_tl,
    縱坐標 .tl_set:N = \l__luatexcn_textbox_y_tl,
    字號 .tl_set:N = \l__luatexcn_textbox_font_size_tl,
    字體顏色 .tl_set:N = \l__luatexcn_textbox_font_color_tl,
    格寬 .meta:n = { grid-width = #1 },
    格高 .meta:n = { grid-height = #1 },
    邊框形狀 .tl_set:N = \l__luatexcn_textbox_border_shape_tl,
    邊框色 .tl_set:N = \l__luatexcn_textbox_border_color_tl,
    邊框寬 .tl_set:N = \l__luatexcn_textbox_border_width_tl,
    邊框間距 .tl_set:N = \l__luatexcn_textbox_border_margin_tl,
    邊框橫距 .tl_set:N = \l__luatexcn_textbox_border_margin_x_tl,
    邊框縱距 .tl_set:N = \l__luatexcn_textbox_border_margin_y_tl,
    外間距 .tl_set:N = \l__luatexcn_textbox_outer_margin_tl,
    外邊框 .bool_set:N = \l__luatexcn_textbox_outer_border_bool,
    外邊框粗細 .tl_set:N = \l__luatexcn_textbox_outer_border_thickness_tl,
    外邊框間距 .tl_set:N = \l__luatexcn_textbox_outer_border_sep_tl,
  }
\ExplSyntaxOff

\endinput%
