%%% ----------------------------------------------------------------------------
%%% suanpan: draw suanpan(abacus) with LaTeX3
%%% Author    : Nan Geng <nangeng@nwafu.edu.cn>
%%% Repository: https://gitee.com/nwafu_nan/suan-span
%%% License   : The LaTeX Project Public License 1.3c
%%% ----------------------------------------------------------------------------
%%% 优化版本 v2.0.0
%%% 优化内容:
%%%   1. 完善错误报告机制
%%%   2. 缓存静态组件，避免重复绘制
%%%   3. 算珠批量渲染，减少 TikZ 环境开销
%%% ----------------------------------------------------------------------------

\NeedsTeXFormat{LaTeX2e}[2020/10/01]
\RequirePackage{expl3}
\ProvidesExplPackage{suanpan-l3}{2026-08-04}{v2.0.0}
  {draw suanpan(abacus) with LaTeX3}

\RequirePackage{l3keys2e}
\RequirePackage{xparse}
\RequirePackage{l3draw}
\RequirePackage{tikz}

% 添加 tikz 宏包的库
\usetikzlibrary{shadings}

% ============================================================================
% 错误报告机制
% ============================================================================

% 错误消息定义
\msg_new:nnn { suanpan } { unknown-option }
  { Package~ option~ "\l_keys_key_tl"~ is~ unknown. }

\msg_new:nnn { suanpan } { bead-type-error }
  { Bead~ type~ "#1"~ is~ unknown.~ Valid~ types:~ 0~(outer),~ 1~(inner),~ 2~(float). }

\msg_new:nnn { suanpan } { rod-value-error }
  { Rod~ value~ "#1"~ is~ out~ of~ range.~ Valid~ range:~ 0--\int_use:N \g__suanpan_num_rods_int. }

\msg_new:nnn { suanpan } { rod-count-error }
  { At~ least~ one~ rod~ must~ be~ defined.~ Use~ \rod~ or~ \rods~ command. }

\msg_new:nnn { suanpan } { mark-list-error }
  { Mark~ list~ is~ empty.~ Please~ provide~ at~ least~ one~ mark. }

\msg_new:nnn { suanpan } { dimension-error }
  { Dimension~ "#1"~ must~ be~ positive.~ Current~ value:~ \dim_use:N #1 }

\msg_new:nnn { suanpan } { bead-position-error }
  { Bead~ position~ "#1"~ is~ out~ of~ range.~ Valid~ range:~ 1--11. }

\msg_new:nnn { suanpan } { rod-index-error }
  { Rod~ index~ "#1"~ is~ out~ of~ range.~ Valid~ range:~ 1~ to~ \int_use:N \l__suanpan_rod_max_int. }

\msg_new:nnn { suanpan } { frame-range-error }
  { Frame~ range~ "#1"~ to~ "#2"~ is~ invalid.~ Start~ must~ be~ less~ than~ end. }

\msg_new:nnn { suanpan } { color-error }
  { Color~ specification~ "#1"~ is~ invalid. }

\msg_new:nnn { suanpan } { start-end-range-error }
  { Range~ specification~ "#1"~ to~ "#2"~ is~ invalid.~ Valid~ range:~
    0 <= start <= end <= \int_use:N \g__suanpan_num_rods_int. }

\msg_new:nnn { suanpan } { step-range-error }
  { Step~ value~ "#1"~ must~ be~ positive. }

% 错误报告辅助函数
\cs_new:Npn \__suanpan_error:n #1
  { \msg_error:nn { suanpan } { #1 } }

\cs_new:Npn \__suanpan_error:nn #1#2
  { \msg_error:nnn { suanpan } { #1 } { #2 } }

\cs_new:Npn \__suanpan_error:nnn #1#2#3
  { \msg_error:nnnn { suanpan } { #1 } { #2 } { #3 } }

% 警告消息定义
\msg_new:nnn { suanpan } { scale-warning }
  { Scale~ factor~ "#1"~ is~ too~ small.~ Using~ 0.1~ instead. }

%============================================================
% 参数检查函数
%============================================================
% 档位范围检查(0-\g__suanpan_num_rods_int)
\cs_new:Npn \__suanpan_check_rod_value:n #1
  {
    \int_compare:nNnTF { #1 } < { 0 }
      { \__suanpan_error:nn { rod-value-error } { #1 } }
      {
        \int_compare:nNnT { #1 } > { \g__suanpan_num_rods_int }
          { \__suanpan_error:nn { rod-value-error } { #1 } }
      }
  }

% 算珠位置范围检查(1-11)
\cs_new:Npn \__suanpan_check_bead_position:n #1
  {
    \int_compare:nNnTF { #1 } < { 1 }
      { \__suanpan_error:nn { bead-position-error } { #1 } }
      {
        \int_compare:nNnT { #1 } > { 11 }
          { \__suanpan_error:nn { bead-position-error } { #1 } }
      }
  }

% 尺寸检查(必须是正值)
\cs_new:Npn \__suanpan_check_positive_dim:N #1
  {
    \dim_compare:nNnT { #1 } < { 0pt }
      { \__suanpan_error:nn { dimension-error } { \token_to_str:N #1 } }
  }

% 档位索引值检查([1-max])
\cs_new:Npn \__suanpan_check_rod_index:n #1
  {
    \int_compare:nNnT { #1 } > { \l__suanpan_rod_max_int }
      { \__suanpan_error:nn { rod-index-error } { #1 } }
    \int_compare:nNnT { #1 } < { 1 }
      { \__suanpan_error:nn { rod-index-error } { #1 } }
  }

% ============================================================================
% 颜色处理函数
% ============================================================================
\cs_new_nopar:Nn \__suanpan_aux_color_boxfill:
  { }

% 颜色命名函数 - 基本版本
% #1 颜色名称
% #2 颜色表达式
\cs_new_protected:Npn \__suanpan_color_select:nn #1#2
  {
    \colorlet { #1 } { #2 }
    % \color_set:nn { #1 } { #2 } % TikZ无法使用，留待l3draw使用
  }
\cs_generate_variant:Nn \__suanpan_color_select:nn { nx }

% 颜色命名函数 - 指定颜色空间版本
% #1 颜色名称
% #2 颜色空间 (rgb, cmyk, gray, etc.)
% #3 颜色分量值
\cs_new_protected:Npn \__suanpan_color_select:nnn #1#2#3
  {
    \definecolor { #1 } { #2 } { #3 }
    % \color_set:nnn { #1 } { #2 } { #3 } % TikZ无法使用，留待l3draw使用
  }
\cs_generate_variant:Nn \__suanpan_color_select:nnn { nnx }

% ============================================================================
% 尺寸变量定义
% ============================================================================
% 算盘基础尺寸
\dim_new:N \l__suanpan_bead_h_dim   % 算珠高度
\dim_new:N \l__suanpan_bead_d_dim   % 算珠直径
\dim_new:N \l__suanpan_rod_d_dim    % 档杆直径
\dim_new:N \l__suanpan_frame_b_dim  % 边框宽度
\dim_new:N \l__suanpan_bead_sep_dim % 算珠间距
\dim_new:N \l__suanpan_rod_sep_dim  % 档杆间距
% 算盘基础尺寸半长度
\dim_new:N \l__suanpan_bead_h_half_dim
\dim_new:N \l__suanpan_bead_d_half_dim
\dim_new:N \l__suanpan_rod_d_half_dim
\dim_new:N \l__suanpan_frame_b_half_dim

% 算盘基础尺寸初值(参考 QBT1747-1993 算盘标准)
\dim_set:Nn \l__suanpan_bead_h_dim   { 12mm  }
\dim_set:Nn \l__suanpan_bead_d_dim   { 23mm  }
\dim_set:Nn \l__suanpan_rod_d_dim    { 7mm   }
\dim_set:Nn \l__suanpan_frame_b_dim  { 13mm  }
\dim_set:Nn \l__suanpan_bead_sep_dim { 1.8pt }
\dim_set:Nn \l__suanpan_rod_sep_dim  { 3.0pt }

\dim_new:N \l__suanpan_bead_upper_offset_dim        % 上珠偏移
\dim_new:N \l__suanpan_bead_lower_offset_dim        % 下珠偏移
\dim_new:N \l__suanpan_rod_upper_h_dim              % 上档杆长度
\dim_new:N \l__suanpan_rod_lower_h_dim              % 下档杆长度
\dim_new:N \l__suanpan_rod_upper_offset_dim         % 上档杆偏移
\dim_new:N \l__suanpan_rod_lower_offset_dim         % 下档杆偏移
\dim_new:N \l__suanpan_rod_upper_dim                % 上档杆位置
\dim_new:N \l__suanpan_rod_lower_dim                % 下档杆位置
\dim_new:N \l__suanpan_frame_upper_inner_offset_dim % 上边框内线偏移
\dim_new:N \l__suanpan_frame_lower_inner_offset_dim % 下边框内线偏移
\dim_new:N \l__suanpan_frame_upper_outer_offset_dim % 上边框外线偏移
\dim_new:N \l__suanpan_frame_lower_outer_offset_dim % 下边框外线偏移
\dim_new:N \l__suanpan_bead_upper_dim               % 算珠上边缘
\dim_new:N \l__suanpan_bead_lower_dim               % 算珠下边缘
\dim_new:N \l__suanpan_bead_draw_h_dim              % 算珠绘制高度
\dim_new:N \l__suanpan_bead_draw_h_half_dim         % 算珠绘制半高度
\dim_new:N \l__suanpan_bead_arc_dim                 % 算珠圆角半径
\dim_new:N \l__suanpan_frame_unit_r_dim             % 横梁计位点半径
\dim_new:N \l__suanpan_frame_outer_half_dim         % 边框外线半宽度
\dim_new:N \l__suanpan_support_d_dim                % 档杆总成宽度
\dim_new:N \l__suanpan_support_d_half_dim           % 档杆总成半宽度
\dim_new:N \l__suanpan_support_y_offset_dim         % 档杆总成组装垂直偏移
\dim_new:N \l__suanpan_frame_inner_upper_dim        % 上边框内线位置
\dim_new:N \l__suanpan_frame_inner_lower_dim        % 下边框内线位置
\dim_new:N \l__suanpan_frame_inner_h_dim            % 边框内线高度
\dim_new:N \l__suanpan_frame_outer_h_dim            % 边框外线高度
\dim_new:N \l__suanpan_frame_inner_linewidth_dim    % 边框内线绘制线宽
\dim_new:N \l__suanpan_frame_outer_linewidth_dim    % 边框外线绘制线宽
\dim_new:N \l__suanpan_rod_linewidth_dim            % 档杆绘制线宽
\dim_new:N \l__suanpan_bead_linewidth_dim           % 算珠绘制线宽

% ============================================================================
% 状态变量
% ============================================================================
\bool_new:N \l__suanpan_support_unit_bool  % 是否绘制计位点
\bool_new:N \l__suanpan_draft_bool         % 是否为草稿模式(不带高光)
\bool_new:N \l__suanpan_empty_rod_bool     % 是否绘制空档(值为0)算珠

% 脏标志 - 标记组件是否需要重建（仅在参数变化时设置）
\bool_new:N \g__suanpan_inner_bead_dirty_bool % 内珠是否需要重建
\bool_new:N \g__suanpan_outer_bead_dirty_bool % 外珠是否需要重建
\bool_new:N \g__suanpan_support_dirty_bool    % 档杆是否需要重建
\bool_new:N \g__suanpan_unit_mark_dirty_bool  % 计位点是否需要重建
\bool_new:N \g__suanpan_float_bead_dirty_bool % 浮珠是否需要重建

% 初始化脏标志
\bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
\bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
\bool_gset_true:N \g__suanpan_support_dirty_bool
\bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
\bool_gset_true:N \g__suanpan_float_bead_dirty_bool

% 初始化计位点标志
\bool_set_false:N \l__suanpan_support_unit_bool

% ============================================================================
% 容器(盒子)定义
% ============================================================================
\coffin_new:N \g__suanpan_inner_bead_coffin % 内珠盒子
\coffin_new:N \g__suanpan_outer_bead_coffin % 外珠盒子
\coffin_new:N \g__suanpan_float_bead_coffin % 浮珠盒子
\coffin_new:N \g__suanpan_unit_mark_coffin  % 计位点盒子
\coffin_new:N \g__suanpan_support_coffin    % 档杆总成盒子
\coffin_new:N \l_tmpc_coffin                % 临时盒子c
\coffin_new:N \l_tmpd_coffin                % 临时盒子d

% ============================================================================
% 其它变量
% ============================================================================
\tl_new:N \l__suanpan_mark_font_tl       % 横梁标记字体
\tl_new:N \g__suanpan_rod_beads_cache_tl % 缓存当前档位数值
\str_new:N \l__suanpan_unit_mark_str     % 计位点标记字符

\fp_new:N \l__suanpan_scale_fp           % 缩放系数

% 浮珠颜色缓存
\tl_new:N \g__suanpan_float_bead_draw_color_tl
\tl_new:N \g__suanpan_float_bead_fill_color_tl
\tl_gset:Nn \g__suanpan_float_bead_draw_color_tl { }
\tl_gset:Nn \g__suanpan_float_bead_fill_color_tl { }

% 高光角度(0-360整数)
\int_new:N \l__suanpan_spot_angle_int      % 高光角度

% 总档数
\int_new:N \g__suanpan_num_rods_int
% 最大档位值
\int_new:N \l__suanpan_rod_max_int
\int_zero:N \l__suanpan_rod_max_int

% 临时整数
\int_new:N \l_tmpc_int
% 临时tl
\tl_new:N \l_tmpc_tl

% 档位算珠位置列表
\clist_new:N \l__suanpan_beads_pos_clist
% 档位缓存（用于缓存不同档位的算珠组）
\prop_new:N \g__suanpan_rod_beads_cache_prop

% ============================================================================
% 档位数字与算珠位置对照属性表常量(内珠和外珠)
% ============================================================================
% {x, y}==>x: 算珠位置(1-11, 1-7---下珠，8-10---上珠，11---悬珠)
%          y: 算珠类型(0---外珠，1---内珠)
\prop_const_from_keyval:Nn \l__suanpan_rods_val_prop
  {
    0  = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {9, 0}, {10, 0}},
    1  = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {7, 1}, {9, 0}, {10, 0}},
    2  = {{1, 0}, {2, 0}, {3, 0}, {6, 1}, {7, 1}, {9, 0}, {10, 0}},
    3  = {{1, 0}, {2, 0}, {5, 1}, {6, 1}, {7, 1}, {9, 0}, {10, 0}},
    4  = {{1, 0}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {9, 0}, {10, 0}},
    5  = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {8, 1}, {10, 0}},
    6  = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {7, 1}, {8, 1}, {10, 0}},
    7  = {{1, 0}, {2, 0}, {3, 0}, {6, 1}, {7, 1}, {8, 1}, {10, 0}},
    8  = {{1, 0}, {2, 0}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {10, 0}},
    9  = {{1, 0}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {10, 0}},
    10 = {{3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {10, 0}},
    11 = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {7, 1}, {8, 1}, {9 , 1}},
    12 = {{1, 0}, {2, 0}, {3, 0}, {6, 1}, {7, 1}, {8, 1}, {9 , 1}},
    13 = {{1, 0}, {2, 0}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {9 , 1}},
    14 = {{1, 0}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {9 , 1}},
    15 = {{3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {9 , 1}},
    16 = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {7, 1}, {8, 1}, {11, 1}},
    17 = {{1, 0}, {2, 0}, {3, 0}, {6, 1}, {7, 1}, {8, 1}, {11, 1}},
    18 = {{1, 0}, {2, 0}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {11, 1}},
    19 = {{1, 0}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {11, 1}},
    20 = {{3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}, {11, 1}}
  }

% ============================================================================
% 尺寸计算函数
% ============================================================================
\cs_new:Nn \__suanpan_dim_calc:
  {
    % 检查尺寸有效性(确保为正值)
    \__suanpan_check_positive_dim:N \l__suanpan_bead_h_dim
    \__suanpan_check_positive_dim:N \l__suanpan_bead_d_dim
    \__suanpan_check_positive_dim:N \l__suanpan_rod_d_dim
    \__suanpan_check_positive_dim:N \l__suanpan_frame_b_dim

    % 基础尺寸半长度
    \dim_set:Nn \l__suanpan_bead_h_half_dim  { \l__suanpan_bead_h_dim  / 2 }
    \dim_set:Nn \l__suanpan_bead_d_half_dim  { \l__suanpan_bead_d_dim  / 2 }
    \dim_set:Nn \l__suanpan_rod_d_half_dim   { \l__suanpan_rod_d_dim   / 2 }
    \dim_set:Nn \l__suanpan_frame_b_half_dim { \l__suanpan_frame_b_dim / 2 }

    % 上珠偏移(8号算珠)
    \dim_set:Nn \l__suanpan_bead_upper_offset_dim
              { \l__suanpan_frame_b_half_dim + \l__suanpan_bead_h_half_dim }
    % 下珠偏移(1号算珠)
    \dim_set:Nn \l__suanpan_bead_lower_offset_dim
             { -\l__suanpan_frame_b_half_dim - \l__suanpan_bead_h_dim * 7 + \l__suanpan_bead_h_half_dim }
    % 上档杆长度
    \dim_set:Nn \l__suanpan_rod_upper_h_dim { \l__suanpan_bead_h_dim * 3 }
    % 下档杆长度
    \dim_set:Nn \l__suanpan_rod_lower_h_dim { \l__suanpan_bead_h_dim * 7 }
    % 上档杆偏移
    \dim_set:Nn \l__suanpan_rod_upper_offset_dim { \l__suanpan_frame_b_half_dim }
    % 下档杆偏移
    \dim_set:Nn \l__suanpan_rod_lower_offset_dim
              { -\l__suanpan_frame_b_half_dim - \l__suanpan_rod_lower_h_dim }
    % 上档杆位置
    \dim_set:Nn \l__suanpan_rod_upper_dim
              { \l__suanpan_rod_upper_offset_dim + \l__suanpan_rod_upper_h_dim }
    % 下档杆位置
    \dim_set:Nn \l__suanpan_rod_lower_dim { -\l__suanpan_frame_b_half_dim }

    % 上边框内线偏移
    \dim_set:Nn \l__suanpan_frame_upper_inner_offset_dim
              { \l__suanpan_frame_b_half_dim + \l__suanpan_rod_upper_h_dim }
    % 下边框内线偏移
    \dim_set:Nn \l__suanpan_frame_lower_inner_offset_dim
             { -\l__suanpan_frame_b_half_dim - \l__suanpan_rod_lower_h_dim }
    % 上边框外线偏移
    \dim_set:Nn \l__suanpan_frame_upper_outer_offset_dim
              { \l__suanpan_frame_b_dim + \l__suanpan_frame_b_half_dim + \l__suanpan_rod_upper_h_dim }
    % 下边框外线偏移
    \dim_set:Nn \l__suanpan_frame_lower_outer_offset_dim
             { -\l__suanpan_frame_b_dim - \l__suanpan_frame_b_half_dim - \l__suanpan_rod_lower_h_dim }

    % 算珠上边缘
    \dim_set:Nn \l__suanpan_bead_upper_dim
              { \l__suanpan_bead_h_half_dim - \l__suanpan_bead_sep_dim / 2 }
    % 算珠下边缘
    \dim_set:Nn \l__suanpan_bead_lower_dim
             { -\l__suanpan_bead_h_half_dim + \l__suanpan_bead_sep_dim / 2 }
    % 算珠绘制高度
    \dim_set:Nn \l__suanpan_bead_draw_h_dim
              { \l__suanpan_bead_upper_dim - \l__suanpan_bead_lower_dim }
    % 算珠绘制半高度
    \dim_set:Nn \l__suanpan_bead_draw_h_half_dim { \l__suanpan_bead_draw_h_dim / 2 }

    % 算珠圆角半径
    \dim_set:Nn \l__suanpan_bead_arc_dim { \l__suanpan_bead_draw_h_half_dim - 1pt }

    % 横梁计位点半径
    \dim_set:Nn \l__suanpan_frame_unit_r_dim { \fp_to_dim:n { \fp_eval:n { \l__suanpan_frame_b_dim * 0.23 } } }

    % 边框外线半宽度
    \dim_set:Nn \l__suanpan_frame_outer_half_dim { \l__suanpan_frame_outer_linewidth_dim / 2 }

    % 档杆总成宽度
    \dim_set:Nn \l__suanpan_support_d_dim { \l__suanpan_bead_d_dim + \l__suanpan_rod_sep_dim }
    % 档杆总成半宽度
    \dim_set:Nn \l__suanpan_support_d_half_dim { \l__suanpan_bead_d_half_dim + \l__suanpan_rod_sep_dim / 2 }

    % 档杆总成组装垂直偏移
    \dim_set:Nn \l__suanpan_support_y_offset_dim
            { ( \l__suanpan_frame_upper_outer_offset_dim -
                \l__suanpan_frame_lower_outer_offset_dim +
                \l__suanpan_frame_outer_linewidth_dim ) / 2 -
                \l__suanpan_frame_upper_outer_offset_dim -
                \l__suanpan_frame_outer_half_dim
            }

    % 上边框内线位置
    \dim_set:Nn \l__suanpan_frame_inner_upper_dim
              { \l__suanpan_frame_upper_inner_offset_dim +
                \l__suanpan_frame_inner_linewidth_dim / 2 }
    % 下边框内线位置
    \dim_set:Nn \l__suanpan_frame_inner_lower_dim
              { \l__suanpan_frame_lower_inner_offset_dim -
                \l__suanpan_frame_inner_linewidth_dim / 2 }

    % 边框内线高度
    \dim_set:Nn \l__suanpan_frame_inner_h_dim
              { \l__suanpan_rod_upper_h_dim +
                \l__suanpan_rod_lower_h_dim +
                \l__suanpan_frame_b_dim }
    % 边框外线高度
    \dim_set:Nn \l__suanpan_frame_outer_h_dim
              { \l__suanpan_rod_upper_h_dim +
                \l__suanpan_rod_lower_h_dim +
                \l__suanpan_frame_b_dim * 3 }
  }

% ============================================================================
% 选项处理
% ============================================================================
\keys_define:nn { suanpan }
  {
    % 草稿模式
    draft .choice:,
    draft / true  .code:n = {%
                              \bool_set_true:N  \l__suanpan_draft_bool
                              \bool_set_false:N  \l__suanpan_empty_rod_bool
                              % 重建所有组件
                              \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_support_dirty_bool
                              \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                            },
    draft / false .code:n = {%
                              \bool_set_false:N \l__suanpan_draft_bool
                              \bool_set_true:N  \l__suanpan_empty_rod_bool
                              % 重建所有组件
                              \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_support_dirty_bool
                              \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                            },
    draft .default:n = true,
    draft .initial:n = false,

    % 是否绘制档的数值为0的空档算珠
    empty .choice:,
    empty / true  .code:n = {%
                              \bool_set_true:N  \l__suanpan_empty_rod_bool
                              % 重建算珠
                              \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                              % \bool_gset_true:N \g__suanpan_support_dirty_bool
                              \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                            },
    empty / false .code:n = {%
                              \bool_set_false:N  \l__suanpan_empty_rod_bool
                              % 重建算珠
                              \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                              \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                              % \bool_gset_true:N \g__suanpan_support_dirty_bool
                              \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                            },
    empty .default:n = true,
    empty .initial:n = true,

    % 总档数
    numrods .int_gset:N = \g__suanpan_num_rods_int,
    numrods .initial:n = 20,

    unknown .code:n = { \__suanpan_error:n { unknown-option } }
  }

% key_value选项设计
\keys_define:nn { suanpan }
  {
    % 绘图线宽
    linewd .code:n = {
                       \dim_set:Nn \l__suanpan_frame_inner_linewidth_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_frame_inner_linewidth_dim
                       \dim_set:Nn \l__suanpan_frame_outer_linewidth_dim
                         {
                           \fp_to_dim:n { \fp_eval:n { \l__suanpan_frame_inner_linewidth_dim * 7.0 } }
                         }
                       \dim_set:Nn \l__suanpan_rod_linewidth_dim
                         {
                           \fp_to_dim:n { \fp_eval:n { \l__suanpan_frame_inner_linewidth_dim * 0.2 } }
                         }
                       \dim_set:Nn \l__suanpan_bead_linewidth_dim
                         {
                           \fp_to_dim:n { \fp_eval:n { \l__suanpan_frame_inner_linewidth_dim * 0.2 } }
                         }
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_support_dirty_bool
                       \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    linewd .initial:n = 2.0pt,

    % 算珠高度
    beadh .code:n = {%
                       \dim_set:Nn \l__suanpan_bead_h_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_bead_h_dim
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_support_dirty_bool
                       \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    beadh .initial:n = 12mm,

    % 算珠直径
    beadd .code:n = {%
                       \dim_set:Nn \l__suanpan_bead_d_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_bead_d_dim
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_support_dirty_bool
                       \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    beadd .initial:n = 23mm,

    % 档杆直径(应小于算珠直径beadd)
    rodd .code:n = {%
                       \dim_set:Nn \l__suanpan_rod_d_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_rod_d_dim
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_support_dirty_bool
                       % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    rodd .initial:n = 7mm,

    % 边框宽度
    framew .code:n = {%
                       \dim_set:Nn \l__suanpan_frame_b_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_frame_b_dim
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_support_dirty_bool
                       \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    framew .initial:n = 13mm,

    % 档间间距
    rodsep .code:n = {%
                       \dim_set:Nn \l__suanpan_rod_sep_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_rod_sep_dim
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_support_dirty_bool
                       % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    rodsep .initial:n = 3.0pt,

    % 算珠间距
    beadsep .code:n = {%
                       \dim_set:Nn \l__suanpan_bead_sep_dim { #1 }
                       \__suanpan_check_positive_dim:N \l__suanpan_bead_sep_dim
                       \__suanpan_dim_calc:
                       % 重建所有组件
                       \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                       \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                       % \bool_gset_true:N \g__suanpan_support_dirty_bool
                       % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                     },
    beadsep .initial:n = 0.0pt,

    % 缩放参数
    scale .fp_set:N = \l__suanpan_scale_fp,
    scale .initial:n = 1.0,

    % 边框绘图颜色
    framedraw  .code:n = { \__suanpan_color_select:nn { framecolor } { #1 }
                           % 重建档杆
                           % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    framedraw  .initial:n = black,
    framedraw* .code:n = { \__suanpan_color_select:nnn { framecolor } #1
                           % 重建档杆
                           % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },

    % 档杆绘图颜色
    roddraw  .code:n = { \__suanpan_color_select:nn { roddrawcolor } { #1 }
                         % 重建档杆
                         % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                         % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_support_dirty_bool
                         % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                       },
    roddraw  .initial:n = black,
    roddraw* .code:n = { \__suanpan_color_select:nnn { roddrawcolor } #1
                         % 重建档杆
                         % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                         % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_support_dirty_bool
                         % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                       },
    % 档杆填充颜色
    rodfill  .code:n = { \__suanpan_color_select:nn { rodfillcolor } { #1 }
                         % 重建档杆
                         % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                         % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_support_dirty_bool
                         % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                       },
    rodfill  .initial:n = black!20,
    rodfill* .code:n = { \__suanpan_color_select:nnn { rodfillcolor } #1
                         % 重建档杆
                         % \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                         % \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_support_dirty_bool
                         % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                       },

    % 外珠绘图颜色
    outerdraw  .code:n = { \__suanpan_color_select:nn { outerdrawcolor } { #1 }
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    outerdraw  .initial:n = black,
    outerdraw* .code:n = { \__suanpan_color_select:nnn { outerdrawcolor } #1
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    % 外珠填充颜色
    outerfill  .code:n = { \__suanpan_color_select:nn { outerfillcolor } { #1 }
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    outerfill  .initial:n = black!40,
    outerfill* .code:n = { \__suanpan_color_select:nnn { outerfillcolor } #1
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },

    % 内珠绘图颜色
    innerdraw  .code:n = { \__suanpan_color_select:nn { innerdrawcolor } { #1 }
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    innerdraw  .initial:n = black,
    innerdraw* .code:n = { \__suanpan_color_select:nnn { innerdrawcolor } #1
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    % 内珠填充颜色
    innerfill  .code:n = { \__suanpan_color_select:nn { innerfillcolor } { #1 }
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },
    innerfill  .initial:n = black,
    innerfill* .code:n = { \__suanpan_color_select:nnn { innerfillcolor } #1
                           % 重建算珠
                           \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                           \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                           % \bool_gset_true:N \g__suanpan_support_dirty_bool
                           % \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                         },

    % 横梁标记字符格式
    font .tl_set:N = \l__suanpan_mark_font_tl,
    font .initial:n = \bfseries\Large,

    % 算珠高光点位置(角度)
    beadspot .code:n = { \tl_set:Nn \l__suanpan_spot_angle_int { #1 }
                         % 重建所有组件
                         \bool_gset_true:N \g__suanpan_inner_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_outer_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
                         \bool_gset_true:N \g__suanpan_support_dirty_bool
                         \bool_gset_true:N \g__suanpan_unit_mark_dirty_bool
                       },
    beadspot .initial:n = 0,

    unknown .code:n = { \__suanpan_error:n { unknown-option } }
  }

% 选项默认值
\keys_set:nn { suanpan }
  {
    linewd  = 2.0pt,
    rodsep  = 3.0pt,
    beadsep = 1.8pt,
    scale   = 1.0,
    numrods = 21,
    empty   = true,
  }

% 将文档类选项传给suanpan
\ProcessKeysOptions { suanpan }

% ============================================================================
% 构建算珠组件
% ============================================================================
% 构建内珠(靠近横梁，参与计数)容器(盒子)
\cs_new:Nn \__suanpan_inner_bead_construct:
  {
    \dim_set:Nn \l_tmpa_dim
      { \l__suanpan_bead_d_half_dim - \l__suanpan_bead_draw_h_half_dim }

    \tikzset{%
      bead/.style = {% 正常内珠样式(带高光圆角矩形)
        ball~color      = innerfillcolor,
        shading~angle   = \l__suanpan_spot_angle_int,
        line~width      = \l__suanpan_bead_linewidth_dim,
        rounded~corners = \l__suanpan_bead_arc_dim,
      },
      draftline/.style = {% 草稿内珠样式(无高光圆帽线段)
        draw       = innerfillcolor,
        line~cap   = round,
        line~width = \l__suanpan_bead_draw_h_dim,
      }
    }

    \group_begin:
      \hcoffin_gset:Nn \g__suanpan_inner_bead_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            {
              \begin{tikzpicture} % 无高光圆帽线段算珠
                \draw[draftline] (-\l_tmpa_dim, 0) -- (\l_tmpa_dim, 0);
              \end{tikzpicture}
            }{
              \begin{tikzpicture}
                \shadedraw[bead] % 带高光填充圆角矩形算珠
                  (-\l__suanpan_bead_d_half_dim, \l__suanpan_bead_lower_dim) rectangle
                  ( \l__suanpan_bead_d_half_dim, \l__suanpan_bead_upper_dim);
              \end{tikzpicture}
            }
        }
    \group_end:
  }

% 构建外珠(远离横梁，不参与计数)容器(盒子)
\cs_new:Nn \__suanpan_outer_bead_construct:
  {
    \dim_set:Nn \l_tmpa_dim
      { \l__suanpan_bead_d_half_dim - \l__suanpan_bead_draw_h_half_dim }

    \tikzset{%
      bead/.style ={% 正常外珠样式(带高光圆角矩形)
        % shading         = ball,
        ball~color      = outerfillcolor,
        shading~angle   = \l__suanpan_spot_angle_int,
        line~width      = \l__suanpan_bead_linewidth_dim,
        rounded~corners = \l__suanpan_bead_arc_dim,
      },
      draftline/.style = {% 草稿外珠样式(无高光圆帽线段)
        draw       = outerfillcolor,
        line~cap   = round,
        line~width = \l__suanpan_bead_draw_h_dim,
      }
    }

    \group_begin:
      \hcoffin_gset:Nn \g__suanpan_outer_bead_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            {
              \begin{tikzpicture} % 无高光圆帽线段算珠
                \draw[draftline] (-\l_tmpa_dim, 0) -- (\l_tmpa_dim, 0);
              \end{tikzpicture}
            }{
              \begin{tikzpicture}
                \shadedraw[bead] % 带高光填充圆角矩形算珠
                  (-\l__suanpan_bead_d_half_dim, \l__suanpan_bead_lower_dim) rectangle
                  ( \l__suanpan_bead_d_half_dim, \l__suanpan_bead_upper_dim);
              \end{tikzpicture}
            }
        }
    \group_end:
  }

% ============================================================================
% 构建档杆支杆
% ============================================================================
\cs_new:Nn \__suanpan_support_construct:
  {
    % TikZ绘图样式
    \tikzset{%
      rod/.style = {% 档干绘制样式(带高光矩形)
        inner~color   = rodfillcolor!30,
        outer~color   = rodfillcolor,
        shading~angle = \l__suanpan_spot_angle_int,
        draw          = roddrawcolor,
        line~width    = \l__suanpan_rod_linewidth_dim,
      },
      beam/.style = {% 横梁绘制样式(带高光矩形)
        bottom~color  = framecolor!60,
        top~color     = framecolor!60,
        middle~color  = framecolor!5,
        shading~angle = \l__suanpan_spot_angle_int,
      },
      rodline/.style = {% 档杆绘制样式
        draw       = roddrawcolor,
        line~width = \l__suanpan_rod_linewidth_dim,
      },
      frameinner/.style = {% 边框内线绘制样式
        draw       = framecolor,
        line~width = \l__suanpan_frame_inner_linewidth_dim,
      },
      frameouter/.style = {% 边框外线绘制样式
        draw       = framecolor,
        line~width = \l__suanpan_frame_outer_linewidth_dim,
      },
    }

    \group_begin:
      \hcoffin_gset:Nn \g__suanpan_support_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            { % 草稿模式
              \begin{tikzpicture}
                % 下杆
                \draw[rodline]
                  (-\l__suanpan_rod_d_half_dim, \l__suanpan_rod_lower_offset_dim) rectangle
                  ( \l__suanpan_rod_d_half_dim, \l__suanpan_rod_lower_dim);
                % 上杆
                \draw[rodline]
                  (-\l__suanpan_rod_d_half_dim, \l__suanpan_rod_upper_offset_dim) rectangle
                  ( \l__suanpan_rod_d_half_dim, \l__suanpan_rod_upper_dim);

                % 横梁上连线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim, \l__suanpan_frame_b_half_dim) --
                  ( \l__suanpan_support_d_half_dim, \l__suanpan_frame_b_half_dim);
                % 横梁下连线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim, -\l__suanpan_frame_b_half_dim) --
                  ( \l__suanpan_support_d_half_dim, -\l__suanpan_frame_b_half_dim);

                % 上梁下边线
                \draw[frameinner]
                  ( -\l__suanpan_support_d_half_dim, \l__suanpan_frame_upper_inner_offset_dim ) --
                  ( \l__suanpan_support_d_half_dim, \l__suanpan_frame_upper_inner_offset_dim );
                % 上梁上边线
                \draw[frameouter]
                  (-\l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_upper_outer_offset_dim -
                    \l__suanpan_frame_outer_linewidth_dim / 2)
                  --
                  (\l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_upper_outer_offset_dim -
                    \l__suanpan_frame_outer_linewidth_dim / 2);

                % 下梁上边线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim, \l__suanpan_frame_lower_inner_offset_dim) --
                  ( \l__suanpan_support_d_half_dim, \l__suanpan_frame_lower_inner_offset_dim);
                % 下梁下边线
                \draw[frameouter]
                  (-\l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_lower_outer_offset_dim +
                    \l__suanpan_frame_outer_linewidth_dim / 2)
                  --
                  ( \l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_lower_outer_offset_dim +
                    \l__suanpan_frame_outer_linewidth_dim / 2);
              \end{tikzpicture}
            }{
              \begin{tikzpicture}
                % 下杆
                \shadedraw[rod]
                  (-\l__suanpan_rod_d_half_dim, \l__suanpan_rod_lower_offset_dim) rectangle
                  ( \l__suanpan_rod_d_half_dim, \l__suanpan_rod_lower_dim);
                % 上杆
                \shadedraw[rod]
                  (-\l__suanpan_rod_d_half_dim, \l__suanpan_rod_upper_offset_dim) rectangle
                  ( \l__suanpan_rod_d_half_dim, \l__suanpan_rod_upper_dim);

                % 横梁
                \shade[beam]
                  (-\l__suanpan_support_d_half_dim, -\l__suanpan_frame_b_half_dim) rectangle
                  ( \l__suanpan_support_d_half_dim,  \l__suanpan_frame_b_half_dim);
                % 横梁上边线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim,  \l__suanpan_frame_b_half_dim) --
                  ( \l__suanpan_support_d_half_dim,  \l__suanpan_frame_b_half_dim);
                % 横梁下边线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim, -\l__suanpan_frame_b_half_dim) --
                  ( \l__suanpan_support_d_half_dim, -\l__suanpan_frame_b_half_dim);

                % 上梁下边线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim, \l__suanpan_frame_upper_inner_offset_dim) --
                  ( \l__suanpan_support_d_half_dim, \l__suanpan_frame_upper_inner_offset_dim);
                % 上梁上边线
                \draw[frameouter]
                  (-\l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_upper_outer_offset_dim -
                    \l__suanpan_frame_outer_linewidth_dim / 2) --
                  (\l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_upper_outer_offset_dim -
                    \l__suanpan_frame_outer_linewidth_dim / 2);

                % 下梁上边线
                \draw[frameinner]
                  (-\l__suanpan_support_d_half_dim, \l__suanpan_frame_lower_inner_offset_dim) --
                  ( \l__suanpan_support_d_half_dim, \l__suanpan_frame_lower_inner_offset_dim);
                % 下梁下边线
                \draw[frameouter]
                  (-\l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_lower_outer_offset_dim +
                    \l__suanpan_frame_outer_linewidth_dim / 2) --
                  ( \l__suanpan_support_d_half_dim,
                    \l__suanpan_frame_lower_outer_offset_dim +
                    \l__suanpan_frame_outer_linewidth_dim / 2);
              \end{tikzpicture}
            }
        }
    \group_end:
  }

% ============================================================================
% 绘制计位点
% ============================================================================
\cs_new:Nn \__suanpan_unit_mark_construct:
  {
    \group_begin:
      \hcoffin_gset:Nn \g__suanpan_unit_mark_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            {
              \begin{tikzpicture}
                \fill[fill=black] (0pt, 0pt) circle [radius = \l__suanpan_frame_unit_r_dim];
              \end{tikzpicture}
            }{
              \begin{tikzpicture}
                \shade[%
                    % shading       = ball,
                    ball~color    = black,
                    shading~angle = \l__suanpan_spot_angle_int,
                  ] (0pt, 0pt) circle [radius = \l__suanpan_frame_unit_r_dim];
              \end{tikzpicture}
            }
        }
    \group_end:
  }

% 构建浮珠容器(盒子)
% #1 绘制颜色
% #2 填充颜色
\cs_new:Npn \__suanpan_float_bead_construct:nn #1#2
  {
    \dim_set:Nn \l_tmpa_dim
      { \l__suanpan_bead_d_half_dim - \l__suanpan_bead_draw_h_half_dim }

    \tikzset{%
      bead/.style~args = { #1/#2 }{% 正常外珠样式(带高光圆角矩形)
        draw            = #1,
        % shading         = ball,
        ball~color      = #2,
        shading~angle   = \l__suanpan_spot_angle_int,
        line~width      = \l__suanpan_bead_linewidth_dim,
        rounded~corners = \l__suanpan_bead_arc_dim,
      },
      draftline/.style = {% 草稿外珠样式(无高光圆帽线段)
        line~cap   = round,
        line~width = \l__suanpan_bead_draw_h_dim,
      }
    }

    \group_begin:
      \hcoffin_gset:Nn \g__suanpan_float_bead_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            {
              \begin{tikzpicture}
                \draw[draw=#2, draftline] (-\l_tmpa_dim, 0) -- ( \l_tmpa_dim, 0);
              \end{tikzpicture}
            }{
              \begin{tikzpicture}
                \shadedraw[bead=#1/#2]
                  (-\l__suanpan_bead_d_half_dim, \l__suanpan_bead_lower_dim) rectangle
                  ( \l__suanpan_bead_d_half_dim, \l__suanpan_bead_upper_dim);
              \end{tikzpicture}
            }
        }
    \group_end:
  }

% ============================================================================
% 绘制算珠
% ============================================================================
% 在垂直方向指定位置处绘制单个算珠
% #1: 算珠位置编号
% #2: 算珠类型 (0——外珠，1——内珠，2——浮珠)
\cs_new:Npn \__suanpan_draw_single_bead:nn #1#2
  {
    % 确定垂直距离
    \int_compare:nNnTF { #1 } > { 7 }
      {
        \int_compare:nNnTF { #1 } < { 11 }
          { % 上珠
            \dim_set:Nn \l_tmpb_dim { \l__suanpan_bead_upper_offset_dim + \l__suanpan_bead_h_dim * (#1 - 8) }
          }{
            % 悬珠
            \dim_set:Nn \l_tmpb_dim { \l__suanpan_frame_b_half_dim + \l__suanpan_bead_h_dim * 2 }
          }
      }{ % 下珠
        \dim_set:Nn \l_tmpb_dim { \l__suanpan_bead_lower_offset_dim + \l__suanpan_bead_h_dim * (#1 - 1) }
      }

    \draw_scope_begin:
      % 垂直偏移
      \draw_transform_yshift:n { \l_tmpb_dim }

      % 选择并布置算珠基础组件(coffin)
      \int_case:nnF { #2 }
        {
          { 0 } { \draw_coffin_use:Nnn \g__suanpan_outer_bead_coffin { hc } { vc } } % 外珠
          { 1 } { \draw_coffin_use:Nnn \g__suanpan_inner_bead_coffin { hc } { vc } } % 内珠
          { 2 } { \draw_coffin_use:Nnn \g__suanpan_float_bead_coffin { hc } { vc } } % 浮珠
        }{ \__suanpan_error:nn { bead-type-error } { #2 } }
    \draw_scope_end:
  }

% ============================================================================
% 绘制一个档位上所有的算珠
% #1 该档位的值
% ============================================================================
\cs_new:Npn \__suanpan_draw_beads:n #1
  {
    % 数值合法性判断
    \__suanpan_check_rod_value:n { #1 }

    % 取得算珠位置数据
    \prop_get:NnN \l__suanpan_rods_val_prop { #1 } \l_tmpa_tl

    % 构造位置列表
    \clist_clear:N \l__suanpan_beads_pos_clist
    \clist_set:NV \l__suanpan_beads_pos_clist \l_tmpa_tl

    % 循环绘制各算珠
    \clist_map_inline:Nn \l__suanpan_beads_pos_clist
      {
        % 构造一个算珠的位置，类型列表
        \clist_set:Nn \l_tmpa_clist { ##1 }

        \__suanpan_draw_single_bead:nn
          { \clist_item:Nn \l_tmpa_clist { 1 } } % 位置(1-11)
          { \clist_item:Nn \l_tmpa_clist { 2 } } % 类型(0-外珠，1-内珠)
      }
  }

% ============================================================================
% 组装一个档位（使用算珠组缓存）
% #1 档位编号
% #2 档位数值
% ============================================================================
\cs_new:Npn \__suanpan_assemble_rod:nn #1#2
  {
    % 数据有效性判断
    \__suanpan_check_rod_index:n { #1 }
    \__suanpan_check_rod_value:n { #2 }

    \draw_scope_begin:
      % 水平平移到当前档位位置
      \draw_transform_xshift:n { \l__suanpan_support_d_dim * (#1 - 1) }

      % 布置支架
      \draw_coffin_use:Nnnn \g__suanpan_support_coffin
        { hc } { vc } { 0pt, -\l__suanpan_support_y_offset_dim }

      % 布置计位点标记
      \bool_if:NT \l__suanpan_support_unit_bool
        {
          \draw_coffin_use:Nnn \g__suanpan_unit_mark_coffin { hc } { vc }
        }

      % 布置算珠
      \int_compare:nNnTF { #2 } = { 0 }
        {
          \bool_if:NT \l__suanpan_empty_rod_bool
            {
              \__suanpan_draw_beads:n { #2 }
            }
        }{
          \__suanpan_draw_beads:n { #2 }
        }
    \draw_scope_end:
  }

% ============================================================================
% 指定一个档位上浮珠的颜色
% #1 档位编号
% #2 算珠位置(1-11)
% ============================================================================
\cs_new:Npn \__suanpan_float_bead:nn #1#2
  {
    % 数据有效性判断
    \__suanpan_check_rod_index:n { #1 }
    \__suanpan_check_bead_position:n { #2 }

    \draw_scope_begin:
      % 水平平移到当前档位位置
      \draw_transform_xshift:n { \l__suanpan_support_d_dim * (#1 - 1) }
      % 布置一个浮珠
      \__suanpan_draw_single_bead:nn { #2 }{ 2 }
    \draw_scope_end:
  }

% ============================================================================
% 为一个档位添加计数标记
% #1 档位编号
% #2 标记内容
% ============================================================================
\cs_new:Npn \__suanpan_rod_mark:nn #1#2
  {
    % 数据有效性判断
    \__suanpan_check_rod_index:n { #1 }

    % 构造标记容器(盒子)
    \hcoffin_set:Nn \l_tmpa_coffin
      {
        \l__suanpan_mark_font_tl
        #2
      }

    \draw_scope_begin:
      % 水平平移到当前档位位置
      \draw_transform_xshift:n { \l__suanpan_support_d_dim * ( #1 - 1 ) }
      % 布置标记盒子
      \draw_coffin_use:Nnn \l_tmpa_coffin { hc } { vc }
    \draw_scope_end:
  }

% ============================================================================
% 绘制边框
% #1 起始档位编号(左)
% #2 终止档位编号(右)
% ============================================================================
\cs_new:Npn \__suanpan_frame_layout:nn #1#2
  {
    % 数据有效性判断
    \int_compare:nNnT { #2 } < { #1 }
      {
        \__suanpan_error:nnn { frame-range-error } { #1 } { #2 }

      }

    % TikZ绘制样式
    \tikzset{%
      hframe/.style = {% 水平梁(上下梁)
        bottom~color  = framecolor!95,
        top~color     = framecolor!95,
        middle~color  = framecolor!15,
        shading~angle = \l__suanpan_spot_angle_int,
        draw          = framecolor,
        line~width    = \l__suanpan_frame_inner_linewidth_dim,
      },
      vframe/.style = {% 垂直梁(左右梁)
        left~color    = framecolor!95,
        right~color   = framecolor!95,
        middle~color  = framecolor!15,
        shading~angle = \l__suanpan_spot_angle_int,
        draw          = framecolor,
        line~width    = \l__suanpan_frame_inner_linewidth_dim,
      },
      mframe/.style = {% 中间横梁
        top~color     = framecolor!60,
        bottom~color  = framecolor!60,
        middle~color  = framecolor!5,
        shading~angle = \l__suanpan_spot_angle_int,
        line~width    = \l__suanpan_frame_inner_linewidth_dim,
      },
      frameline/.style = {% 绘制线条样式
        draw       = framecolor,
        line~width = \l__suanpan_frame_inner_linewidth_dim,
      },
      framefill/.style = {% 填充样式
        draw       = framecolor,
        fill       = framecolor,
        line~width = \l__suanpan_frame_inner_linewidth_dim,
      },
      arcline/.style = {% 转角线
        draw            = framecolor,
        line~width      = \l__suanpan_frame_inner_linewidth_dim,
        rounded~corners = \l__suanpan_frame_inner_linewidth_dim / 2,
      },
    }

    % 总宽度
    \dim_set:Nn \l_tmpa_dim {
       \fp_to_dim:n {
           \fp_eval:n { \l__suanpan_support_d_dim * ( #2 - #1 + 1.0) }
         }
     }
    % 减半档宽度
    \dim_set:Nn \l_tmpb_dim {
       \fp_to_dim:n {
           \fp_eval:n { \l__suanpan_support_d_dim * ( #2 - #1 ) +
                        \l__suanpan_support_d_half_dim}
         }
     }

    \group_begin:
      % 上下边框基础容器(盒子)
      \hcoffin_set:Nn \l_tmpa_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            { % 草稿模式
              \begin{tikzpicture}
                \draw[framefill]
                    ( 0pt, 0pt ) --
                  ++( \l_tmpa_dim, 0) --
                  ++( \l__suanpan_frame_b_dim, \l__suanpan_frame_b_dim ) --
                  ++( -\l__suanpan_frame_b_dim, 0 ) --
                  ++( -\l_tmpa_dim, 0 ) --
                  ++( -\l__suanpan_frame_b_dim, 0 ) --
                  cycle;
              \end{tikzpicture}
            }{ % 正常模式
              \begin{tikzpicture}
                \shadedraw[hframe]
                    ( 0pt, 0pt ) --
                  ++( \l_tmpa_dim, 0) --
                  ++( \l__suanpan_frame_b_dim, \l__suanpan_frame_b_dim ) --
                  ++( -\l__suanpan_frame_b_dim, 0 ) --
                  ++( -\l_tmpa_dim, 0 ) --
                  ++( -\l__suanpan_frame_b_dim, 0 ) --
                  cycle;
              \end{tikzpicture}
            }
        }

      % 左右边框基础容器(盒子)
      \hcoffin_set:Nn \l_tmpb_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            { % 草稿模式
              \begin{tikzpicture}
                \draw[framefill]
                    ( 0, 0 ) --
                  ++( 0, \l__suanpan_frame_inner_h_dim) --
                  ++( -\l__suanpan_frame_b_dim, \l__suanpan_frame_b_dim ) --
                  ++( 0, -\l__suanpan_frame_outer_h_dim ) --
                  cycle;
                \draw[arcline]
                    ( 0, 0 )
                  ++( 0,  \l__suanpan_frame_inner_h_dim )
                  ++( 0,  \l__suanpan_frame_b_dim )
                  ++( 0,  \l__suanpan_frame_inner_linewidth_dim * 0.9 )
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_inner_linewidth_dim * 0.9, 0 ) --
                  ++( 0, -\l__suanpan_frame_b_half_dim );
                \draw[arcline]
                    ( 0, 0 )
                  ++( 0, -\l__suanpan_frame_b_dim )
                  ++( 0, -\l__suanpan_frame_inner_linewidth_dim * 0.9 )
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_inner_linewidth_dim * 0.9, 0 ) --
                  ++( 0, \l__suanpan_frame_b_half_dim );
              \end{tikzpicture}
            }{ % 正常模式
              \begin{tikzpicture}
                \shadedraw[vframe]
                    ( 0, 0 ) --
                  ++( 0, \l__suanpan_frame_inner_h_dim) --
                  ++( -\l__suanpan_frame_b_dim, \l__suanpan_frame_b_dim ) --
                  ++( 0, -\l__suanpan_frame_outer_h_dim ) --
                  cycle;
                \draw[arcline]
                    ( 0, 0 )
                  ++( 0,  \l__suanpan_frame_inner_h_dim )
                  ++( 0,  \l__suanpan_frame_b_dim )
                  ++( 0,  \l__suanpan_frame_inner_linewidth_dim * 0.9 )
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_inner_linewidth_dim * 0.9, 0 ) --
                  ++( 0, -\l__suanpan_frame_b_half_dim );
                \draw[arcline]
                    ( 0, 0 )
                  ++( 0, -\l__suanpan_frame_b_dim )
                  ++( 0, -\l__suanpan_frame_inner_linewidth_dim * 0.9 )
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_b_half_dim, 0 ) --
                  ++( -\l__suanpan_frame_inner_linewidth_dim * 0.9, 0 ) --
                  ++( 0, \l__suanpan_frame_b_half_dim );
              \end{tikzpicture}
            }
        }

      % 横梁接头基础容器(盒子)
      \hcoffin_set:Nn \l_tmpc_coffin
        {
          \bool_if:NTF \l__suanpan_draft_bool
            { % 草稿模式
              \begin{tikzpicture}
                \fill[fill=white, line~width=\l__suanpan_frame_inner_linewidth_dim]
                  ( 0pt, 0pt ) --++( 0, \l__suanpan_frame_b_dim) --
                  ++(-\l__suanpan_frame_inner_linewidth_dim, 0) --
                  ++(-\l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  ++( \l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  cycle;
                \draw[frameline]%
                  ( 0, \l__suanpan_frame_b_dim) --
                  ++(-\l__suanpan_frame_inner_linewidth_dim, 0) --
                  ++(-\l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  ++( \l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  ++( \l__suanpan_frame_inner_linewidth_dim, 0);
              \end{tikzpicture}
            }{ % 正常模式
              \begin{tikzpicture}
                \shade[mframe]
                  ( 0pt, 0pt ) --++( 0, \l__suanpan_frame_b_dim) --
                  ++(-\l__suanpan_frame_inner_linewidth_dim, 0) --
                  ++(-\l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  ++( \l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  cycle;
                \draw[frameline]%
                  ( 0, \l__suanpan_frame_b_dim) --
                  ++(-\l__suanpan_frame_inner_linewidth_dim, 0) --
                  ++(-\l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  ++( \l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2) --
                  ++( \l__suanpan_frame_inner_linewidth_dim, 0);
              \end{tikzpicture}
            }
        }

    % 布置上边框
    \draw_scope_begin:
      \draw_transform_xshift:n { -\l__suanpan_support_d_half_dim -
                                  \l__suanpan_frame_inner_linewidth_dim / 2 }
      \draw_transform_yshift:n { \l__suanpan_frame_inner_upper_dim -
                                 \l__suanpan_frame_inner_linewidth_dim }
      \draw_coffin_use:Nnnn \l_tmpa_coffin { l } { b }
                         { -\l__suanpan_frame_b_dim, 0pt }
    \draw_scope_end:

    % 布置下边框
    \draw_scope_begin:
      \coffin_rotate:Nn \l_tmpa_coffin { 180 }
      \draw_transform_xshift:n { \l_tmpa_dim - \l__suanpan_support_d_half_dim +
                                 \l__suanpan_frame_inner_linewidth_dim / 2 }
      \draw_transform_yshift:n { \l__suanpan_frame_inner_lower_dim +
                                 \l__suanpan_frame_inner_linewidth_dim }
      \draw_coffin_use:Nnnn \l_tmpa_coffin { l } { b }
                          { \l__suanpan_frame_b_dim, 0pt }
    \draw_scope_end:

    % 布置左边框
    \draw_scope_begin:
      \draw_transform_xshift:n { -\l__suanpan_support_d_half_dim -
                                  \l__suanpan_frame_b_dim -
                                  \l__suanpan_frame_inner_linewidth_dim * 1.5 }
      \draw_transform_yshift:n { \l__suanpan_frame_inner_lower_dim -
                                  \l__suanpan_frame_inner_linewidth_dim }
      \draw_coffin_use:Nnnn \l_tmpb_coffin { l } { b }
                          { 0pt, -\l__suanpan_frame_b_dim }
    \draw_scope_end:

    % 布置右边框
    \draw_scope_begin:
      \coffin_rotate:Nn \l_tmpb_coffin { 180 }
      \draw_transform_xshift:n { \l_tmpb_dim +
                                 \l__suanpan_frame_b_dim +
                                 \l__suanpan_frame_inner_linewidth_dim * 1.5}
      \draw_transform_yshift:n { \l__suanpan_frame_inner_upper_dim +
                                 \l__suanpan_frame_inner_linewidth_dim }
      \draw_coffin_use:Nnnn \l_tmpb_coffin { l } { b }
                          { 0pt, \l__suanpan_frame_b_dim }
    \draw_scope_end:

    % 布置左横梁接头
    \draw_scope_begin:
      \draw_transform_xshift:n { -\l__suanpan_support_d_half_dim -
                                  \l__suanpan_frame_b_dim / 3 -
                                  \l__suanpan_frame_inner_linewidth_dim / 2 }
      \draw_transform_yshift:n { -\l__suanpan_frame_b_dim / 2 -
                                  \l__suanpan_frame_inner_linewidth_dim / 2 }
      \draw_coffin_use:Nnnn \l_tmpc_coffin { l } { b } { 0pt, 0pt }
    \draw_scope_end:

    % 布置右横梁接头
    \draw_scope_begin:
      \coffin_rotate:Nn \l_tmpc_coffin { 180 }
      \draw_transform_xshift:n { \l_tmpb_dim +
                                 \l__suanpan_frame_b_dim / 3 +
                                 \l__suanpan_frame_inner_linewidth_dim / 2 }
      \draw_transform_yshift:n { \l__suanpan_frame_b_dim / 2 +
                                 \l__suanpan_frame_inner_linewidth_dim / 2 }
      \draw_coffin_use:Nnnn \l_tmpc_coffin { l } { b } { 0pt, 0pt }
    \draw_scope_end:
    \group_end:
  }

% ============================================================================
% 解析档位数字列表（支持区间语法）
% #1 输出的 clist 变量（解析结果添加到此 clist 中）
% #2 原始数据（可能包含区间数据，如 "4-7"）
% ============================================================================
\cs_new:Npn \__suanpan_parse_rod_list:Nn #1#2
  {
    \clist_clear:N #1
    \clist_set:Nn \l_tmpb_clist { #2 }

    \clist_map_inline:Nn \l_tmpb_clist
      {
        \tl_set:Nn \l_tmpa_tl { ##1 }
        \tl_if_in:NnTF \l_tmpa_tl { - }
          {
            \__suanpan_parse_rod_range:Nx #1 { \l_tmpa_tl }
          }
          {
            \clist_put_right:NV #1 \l_tmpa_tl
          }
      }
  }

% ============================================================================
% 解析区间 "start-end" 或 "start-end-step"
% #1 输出的 clist 变量
% #2 区间字符串（如 "4-7"或"4-7-2"）
% ============================================================================
\cs_new:Npn \__suanpan_parse_rod_range:Nn #1#2
  {
    \seq_set_split:Nnn \l_tmpa_seq { - } { #2 }
    \seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl  % start
    \seq_pop_right:NN \l_tmpa_seq \l_tmpb_tl % end/step

    \seq_if_empty:NTF \l_tmpa_seq
      {
        % \l_tmpa_seq队列已空，为start-end 格式
        \int_set:Nn \l_tmpa_int { \l_tmpa_tl }
        \int_set:Nn \l_tmpb_int { \l_tmpb_tl }
        \__suanpan_check_range:VV \l_tmpa_int \l_tmpb_int
        \int_step_inline:nnnn { \l_tmpa_int } { 1 } { \l_tmpb_int }
          {
            \clist_put_right:Nn #1 { ##1 }
          }
      }
      {
        % \l_tmpa_seq队列未空，为start-end-step 格式
        \seq_pop_left:NN \l_tmpa_seq \l_tmpc_tl % end
        \int_set:Nn \l_tmpa_int { \l_tmpa_tl }  % start
        \int_set:Nn \l_tmpb_int { \l_tmpc_tl }  % end
        \int_set:Nn \l_tmpc_int { \l_tmpb_tl }  % step
        \__suanpan_check_range:VVV \l_tmpa_int \l_tmpb_int \l_tmpc_int
        \int_step_inline:nnnn { \l_tmpa_int } { \l_tmpc_int } { \l_tmpb_int }
          {
            \clist_put_right:Nn #1 { ##1 }
          }
      }
  }
\cs_generate_variant:Nn \__suanpan_parse_rod_range:Nn { Nx }

% ============================================================================
% 区间数据范围检查函数
% ============================================================================
\cs_new:Npn \__suanpan_check_range:nn #1#2
  {
    \int_compare:nNnT { #1 } > { #2 }
      {
        \__suanpan_error:nnn { start-end-range-error } { #1 } { #2 }
      }
    \int_compare:nNnT { #1 } < { 0 }
      {
        \__suanpan_error:nnn { start-end-range-error } { #1 } { #2 }
      }
    \int_compare:nNnT { #2 } > { \g__suanpan_num_rods_int }
      {
        \__suanpan_error:nnn { start-end-range-error } { #1 } { #2 }
      }
  }
\cs_generate_variant:Nn \__suanpan_check_range:nn { VV }

\cs_new:Npn \__suanpan_check_range:nnn #1#2#3
  {
    \int_compare:nNnT { #1 } > { #2 }
      {
        \__suanpan_error:nnn { start-end-range-error } { #1 } { #2 }
      }
    \int_compare:nNnT { #1 } < { 0 }
      {
        \__suanpan_error:nnn { start-end-range-error } { #1 } { #2 }
      }
    \int_compare:nNnT { #2 } > { \g__suanpan_num_rods_int }
      {
        \__suanpan_error:nnn { start-end-range-error } { #1 } { #2 }
      }
    \int_compare:nNnT { #3 } < { 1 }
      {
        \__suanpan_error:nn { step-range-error } { #3 }
      }
  }
\cs_generate_variant:Nn \__suanpan_check_range:nnn { VVV }

% ============================================================================
% 用户接口环境
% ============================================================================
% 算盘排版环境
% #1 suanpan环境选项
% #2 suanpan环境内容(!b参数)
\NewDocumentEnvironment{ suanpan }{ o !b }
  {
    \group_begin:
      % 处理选项
      \IfNoValueF{ #1 } { \keys_set:nn { suanpan }{ #1 } }

      % 删除#2(!b)参数取得的环境内容中的空白
      \str_set:Nn \l_tmpa_str { #2 }
      \str_remove_all:Nn \l_tmpa_str { ~ }
      % 将str还原为tl
      \tl_set_rescan:Nno \l_tmpa_tl { } { \l_tmpa_str }

      % 按需构建基础容器(盒子) - 仅在第一次和参数变化时重建
      % 内珠
      \bool_if:NT \g__suanpan_inner_bead_dirty_bool
        {
          \__suanpan_inner_bead_construct:
          \bool_gset_false:N \g__suanpan_inner_bead_dirty_bool
        }
      % 外珠
      \bool_if:NT \g__suanpan_outer_bead_dirty_bool
        {
          \__suanpan_outer_bead_construct:
          \bool_gset_false:N \g__suanpan_outer_bead_dirty_bool
        }
      % 档杆
      \bool_if:NT \g__suanpan_support_dirty_bool
        {
          \__suanpan_support_construct:
          \bool_gset_false:N \g__suanpan_support_dirty_bool
        }
      % 计位点(圆点)
      \bool_if:NT \g__suanpan_unit_mark_dirty_bool
        {
          \__suanpan_unit_mark_construct:
          \bool_gset_false:N \g__suanpan_unit_mark_dirty_bool
        }

      % 在容器(盒子)中用#2(!b)参数取得的环境内容实现绘制
      \hcoffin_set:Nn \l_tmpa_coffin
        {
          \draw_begin:
            % 执行用户内容
            \l_tmpa_tl
            % 检查是否有档位定义
            \int_compare:nNnT { \l__suanpan_rod_max_int } = { 0 }
              {
                \__suanpan_error:n { rod-count-error }
              }
          \draw_end:
        }

      \hcoffin_set:Nn \l_tmpb_coffin
        {
          % 将原图缩小50%(有意为之，可以不缩小)
          \coffin_scale:Nnn \l_tmpa_coffin { 0.50 } { 0.50 }
          \coffin_typeset:Nnnnn \l_tmpa_coffin { l } { b } { 0pt } { 0pt }
        }
  }{
      % 检查缩放参数
      \fp_compare:nNnT { \l__suanpan_scale_fp } < { 0.1 }
        {
          \msg_warning:nnn { suanpan } { scale-warning } { \l__suanpan_scale_fp }
          \tl_set:Nn \l__suanpan_scale_fp { 0.1 }
        }

      % 按用户指定比例缩放
      \coffin_scale:Nnn \l_tmpb_coffin { \l__suanpan_scale_fp } { \l__suanpan_scale_fp }

      % 设置基字符盒子(排版深度)
      \hbox_set:Nn \l_tmpa_box { x }
      \dim_set:Nn \l_tmpa_dim { \box_dp:N \l_tmpa_box }

      % 输出算盘容器(盒子)
      \coffin_typeset:Nnnnn \l_tmpb_coffin { l } { b } { 0pt } { -\l_tmpa_dim }
      \int_zero:N \l__suanpan_rod_max_int
    \group_end:
  }

% ============================================================================
% 用户接口命令
% ============================================================================
% 排版算盘一个档位，包括档杆和算珠
% #1 星号命令，绘制计位点(黑色圆点)
% #2 档位编号(基于1，从左向右计数)
% #3 本档数字(0-20，10-15需用到顶珠和底珠，16-20需要用到悬珠)
\NewDocumentCommand{ \rod }{ s m m }
  {
    \int_set:Nn \l__suanpan_rod_max_int
      { \int_max:nn { \l__suanpan_rod_max_int }{ #2 } }

    % 星号命令，用于在横梁绘制计位点
    \IfBooleanTF{ #1 }
      {
        \bool_set_true:N \l__suanpan_support_unit_bool
      }{
        \bool_set_false:N \l__suanpan_support_unit_bool
      }

    % 组装一个档位
    \__suanpan_assemble_rod:nn { #2 }{ #3 }
  }

% 排版算盘多个档位，包括档杆和算珠
% #1 本档数字(0-20，10-15需用到顶珠和底珠，16-20需要用到悬珠)
%    支持区间语法，"start-end: 4-7" 表示 "4,5,6,7"；"start-end-step: 4-7-2" 表示"4,6"。
%    各数字之间需要用逗号分隔。
% #2 计位点标记可选项（字符串，每个字符对应一档，* 表示有计位点，空白会被忽略）
\NewDocumentCommand{ \rods }{ m O{} }
  {
    % 构造档位数字列表
    % \clist_set:Nn \l_tmpa_clist { #1 }
    % 解析档位数字列表（支持区间语法）
    \__suanpan_parse_rod_list:Nn \l_tmpa_clist { #1 }

    % 初始化档位计数器
    \int_set:Nn \l_tmpa_int { 0 }

    % 统计档位总数
    \int_set:Nn \l__suanpan_rod_max_int { \clist_count:N \l_tmpa_clist }

    % 处理计位点标记字符串(忽略所有空格)
    \str_set:Nn \l_tmpb_str { #2 }
    \str_remove_all:Nn \l_tmpb_str { \c_space_tl }
    % 获取计位点标记长度
    \int_set:Nn \l_tmpb_int { \str_count:N \l_tmpb_str }

    \str_if_empty:NTF \l_tmpb_str
      {
        % 计位点标记为空，不添加计位点
        \bool_set_false:N \l__suanpan_support_unit_bool

        \clist_map_inline:Nn \l_tmpa_clist
          {
            % 档位计数器自增1
            \int_incr:N \l_tmpa_int
            % 组装当前档位
            \__suanpan_assemble_rod:nn {\l_tmpa_int }{ ##1 }
          }
      }{
        \clist_map_inline:Nn \l_tmpa_clist
          {
            % 档位计数器自增1
            \int_incr:N \l_tmpa_int

            \int_compare:nNnTF { \l_tmpa_int } > { \l_tmpb_int }
              { % 超过计位点标记字符串长度的档位无计位标记
                \bool_set_false:N \l__suanpan_support_unit_bool
              }
              {
                % 处理各个计位点标记(自动忽略超出档数的标记)
                \str_set:Nx \l__suanpan_unit_mark_str { \str_item:Nn \l_tmpb_str { \l_tmpa_int } }
                \str_if_eq:VnTF \l__suanpan_unit_mark_str { * }
                  { \bool_set_true:N \l__suanpan_support_unit_bool }
                  { \bool_set_false:N \l__suanpan_support_unit_bool }
              }

            % 组装当前档位
            \__suanpan_assemble_rod:nn {\l_tmpa_int }{ ##1 }
          }
      }

    % 更新档位总数
    \int_set:Nn \l__suanpan_rod_max_int
      { \int_max:nn { \l__suanpan_rod_max_int }{ \l_tmpa_int } }
  }

% 指定某档上某个算珠的颜色
% #1 档位(基于1，从左向右计数)
% #2 算珠位置列表(1--11，11为悬珠位置)
% #3 算珠填充颜色
% #4 算珠绘制颜色(默认为内珠绘制颜色)
\NewDocumentCommand{ \bead }{ m m m O{innerdrawcolor} }
  {
    % 判断颜色是否变化，或脏标志已为真
    \tl_if_eq:nnF { #4 } { \g__suanpan_float_bead_draw_color_tl }
      {
        \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
        \tl_gset:Nn \g__suanpan_float_bead_draw_color_tl { #4 }
      }
    \tl_if_eq:nnF { #3 } { \g__suanpan_float_bead_fill_color_tl }
      {
        \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
        \tl_gset:Nn \g__suanpan_float_bead_fill_color_tl { #3 }
      }

    \bool_if:NT \g__suanpan_float_bead_dirty_bool
      {
        \__suanpan_float_bead_construct:nn { #4 }{ #3 }
        \bool_gset_false:N \g__suanpan_float_bead_dirty_bool
      }

    % 构造算珠位置列表
    \clist_set:Nn \l_tmpa_clist { #2 }

    \clist_map_inline:Nn \l_tmpa_clist
      {
        % 绘制浮珠
        \__suanpan_float_bead:nn { #1 }{ ##1 }
      }
  }

% 指定某档上所有内珠或外珠的颜色
% #1 星号命令，选择内珠或外珠
% #2 档位(基于1，从左向右计数)
% #3 该档数字
% #4 算珠填充颜色(绘图颜色采用原内/外珠绘制颜色)
\NewDocumentCommand{ \beads }{ s m m m }
  {
    % 数据有效性检查
    \__suanpan_check_rod_index:n { #2 }
    \__suanpan_check_rod_value:n { #3 }

    % 取得档位数字对应算珠位置及内珠/外珠编号
    \prop_get:NnN \l__suanpan_rods_val_prop { #3 } \l_tmpa_tl
    \clist_clear:N \l__suanpan_beads_pos_clist
    \clist_set:NV \l__suanpan_beads_pos_clist \l_tmpa_tl

    \IfBooleanTF{ #1 }
      {
        % 外珠着色
        \tl_if_eq:nnF { outerdrawcolor } { \g__suanpan_float_bead_draw_color_tl }
          {
            \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
            \tl_gset:Nn \g__suanpan_float_bead_draw_color_tl { outerdrawcolor }
          }
      }{
        % 内珠着色
        \tl_if_eq:nnF { innerdrawcolor } { \g__suanpan_float_bead_draw_color_tl }
          {
            \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
            \tl_gset:Nn \g__suanpan_float_bead_draw_color_tl { innerdrawcolor }
          }
      }
    \tl_if_eq:nnF { #4 } { \g__suanpan_float_bead_fill_color_tl }
      {
        \bool_gset_true:N \g__suanpan_float_bead_dirty_bool
        \tl_gset:Nn \g__suanpan_float_bead_fill_color_tl { #4 }
      }

    \bool_if:NT \g__suanpan_float_bead_dirty_bool
      {
        \IfBooleanTF{ #1 }
          { \__suanpan_float_bead_construct:nn { outerdrawcolor }{ #4 } }
          { \__suanpan_float_bead_construct:nn { innerdrawcolor }{ #4 } }
        \bool_gset_false:N \g__suanpan_float_bead_dirty_bool
      }

    \prop_get:NnN \l__suanpan_rods_val_prop { #3 } \l_tmpa_tl
    \clist_clear:N \l__suanpan_beads_pos_clist
    \clist_set:NV \l__suanpan_beads_pos_clist \l_tmpa_tl

    \IfBooleanTF{ #1 }
      {
        \clist_map_inline:Nn \l__suanpan_beads_pos_clist
          {
            \clist_set:Nn \l_tmpa_clist { ##1 }
            \int_compare:nNnT { \clist_item:Nn \l_tmpa_clist { 2 } } = { 0 }
              {
                \__suanpan_float_bead:nn { #2 }{ \clist_item:Nn \l_tmpa_clist { 1 } }
              }
          }
      }{
        \clist_map_inline:Nn \l__suanpan_beads_pos_clist
          {
            \clist_set:Nn \l_tmpa_clist { ##1 }
            \int_compare:nNnT { \clist_item:Nn \l_tmpa_clist { 2 } } = { 1 }
              {
                \__suanpan_float_bead:nn { #2 }{ \clist_item:Nn \l_tmpa_clist { 1 } }
              }
          }
      }
  }

% 绘制边框
\NewDocumentCommand{ \mkframe }{ }
  {
    \int_compare:nNnT { \l__suanpan_rod_max_int } = { 0 }
      {
        \__suanpan_error:n { rod-count-error }
      }
    \__suanpan_frame_layout:nn { 1 }{ \l__suanpan_rod_max_int }
  }

% 为各档添加计位标记
% #1 起始档位(基于1，从左向右计数)
% #2 标记列表
\NewDocumentCommand{ \rodmark }{ O{1} m }
  {
    % 标记列表
    \clist_set:Nn \l_tmpa_clist { #2 }

    % 数据有效性检查
    \clist_if_empty:NT \l_tmpa_clist
      {
        \__suanpan_error:n { mark-list-error }
      }

    % 计数器
    \int_set:Nn \l_tmpa_int { #1 }
    % 有效性检查
    \__suanpan_check_rod_index:n { #1 }

    % 遍历列表
    \clist_map_inline:Nn \l_tmpa_clist
      {
        % 输出标记
        \__suanpan_rod_mark:nn { \l_tmpa_int }{ ##1 }

        % 递增计数器
        \int_incr:N \l_tmpa_int

        % 超出最大档位
        \int_compare:nNnT { \l_tmpa_int } > { \l__suanpan_rod_max_int }
          {
            \clist_map_break:
          }
      }
  }

% 选项设置用户接口
\NewDocumentCommand{ \suanpanset } { m }
  {
    \IfNoValueF { #1 }
      {
        \keys_set:nn { suanpan } { #1 }
      }
  }

\endinput
