%%% ---------------------------------------------------------------------------- %%% suanpan: draw suanpan(abacus) with LaTeX3 %%% Author : Nan Geng %%% Repository: https://gitee.com/nwafu_nan/suan-span %%% License : The LaTeX Project Public License 1.3c %%% ---------------------------------------------------------------------------- \NeedsTeXFormat{LaTeX2e}[2020/10/01] \RequirePackage{expl3} \ProvidesExplPackage{suanpan-l3}{2026-08-12}{v2.1.1} {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--20. } \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 \g__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 <= 20. } \msg_new:nnn { suanpan } { step-range-error } { Step~ value~ "#1"~ must~ be~ positive. } \msg_new:nnn { suanpan } { repeat-value-error } { Repeat~ value~ "#1"~ must~ be~ positive. } \msg_new:nnn { suanpan } { bead-range-syntax-error } { Invalid~ bead~ position~ range~ syntax~ "#1".~ Use~ "start-end"~ (e.g.,~ "4-7"). } \msg_new:nnn { suanpan } { rods-range-syntax-error } { Invalid~ rods~ range~ syntax~ "#1".~ Use~ "start-end"~ (e.g.,~ "4-7")~ or~ "start-end-step"~ (e.g.,~ "4-7-2"). } \msg_new:nnn { suanpan } { rods-repeat-syntax-error } { Invalid~ rods~ repeat~ syntax~ "#1".~ Use~ "value[count]"~ (e.g.,~ "2[3]"). } \msg_new:nnn { suanpan } { bead-range-error } { Bead~ position~ range~ "#1"~ to~ "#2"~ is~ invalid.~ Valid~ range:~ 1 <= start <= end <= 11. } % 错误报告辅助函数 \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-20) \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 } > { 20 } { \__suanpan_error:nn { rod-value-error } { #1 } } } } % 算珠位置范围检查(1-11) % #1: 算珠位置编号 % o o o o o o o | o o o % 1 2 3 4 5 6 7 8 9 10 % | o % 11 % 其中,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 } > { \g__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 } % 系统函数变体处理 \cs_generate_variant:Nn \draw_coffin_use:Nnn { cnn } \cs_generate_variant:Nn \draw_coffin_use:Nnnn { cnnn } % ============================================================================ % 尺寸变量定义 % ============================================================================ % 算盘基础尺寸 \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 算盘标准并缩小0.5倍) \dim_set:Nn \l__suanpan_bead_h_dim { 6mm } \dim_set:Nn \l__suanpan_bead_d_dim { 11.5mm } \dim_set:Nn \l__suanpan_rod_d_dim { 3.5mm } \dim_set:Nn \l__suanpan_frame_b_dim { 6.5mm } \dim_set:Nn \l__suanpan_bead_sep_dim { 0.9pt } \dim_set:Nn \l__suanpan_rod_sep_dim { 1.5pt } \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 % 算珠绘制线宽 \dim_new:N \l__suanpan_support_clip_lower_dim % clip 下边界 \dim_new:N \l__suanpan_support_clip_upper_dim % clip 上边界 \dim_new:N \l__suanpan_support_beam_yshift_dim % 横梁边线 yshift \dim_new:N \l__suanpan_support_upper_inner_yshift_dim % 上梁内线 yshift \dim_new:N \l__suanpan_support_lower_inner_yshift_dim % 下梁内线 yshift \dim_new:N \l__suanpan_support_upper_outer_yshift_dim % 上梁外线 yshift \dim_new:N \l__suanpan_support_lower_outer_yshift_dim % 下梁外线 yshift \dim_new:N \l__suanpan_support_rod_lower_top_extend_dim % 下杆顶端延长 \dim_new:N \l__suanpan_support_rod_lower_bottom_extend_dim % 下杆底端延长 \dim_new:N \l__suanpan_support_rod_upper_top_extend_dim % 上杆顶端延长 \dim_new:N \l__suanpan_support_rod_upper_bottom_extend_dim % 上杆底端延长 \dim_new:N \l_tmpc_dim % 临时尺寸c \dim_new:N \l_tmpd_dim % 临时尺寸d % ============================================================================ % 状态变量 % ============================================================================ \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_new:N \g__suanpan_support_unit_dirty_bool % 0-20的各档位总成是否需要重建 % 初始化脏标志 \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_gset_true:N \g__suanpan_support_unit_dirty_bool % 初始化计位点标志 \bool_set_false:N \l__suanpan_support_unit_bool % 是否使用了\mkframe命令绘制边框 \bool_new:N \g__suanpan_has_frame_bool \bool_gset_false:N \g__suanpan_has_frame_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 % 批量定义0-20共计21个表示不同档位值的档位总成容器(用字母a-u进行区分) \int_step_inline:nn { 21 } { % 用\int_to_alph:n { #1 }函数将数字转换为小写字母 \coffin_new:c { g__suanpan_support_unit_ \int_to_alph:n { #1 } _coffin } } % ============================================================================ % 其它变量 % ============================================================================ \tl_new:N \l__suanpan_mark_font_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_rod_max_int \int_zero:N \g__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 % ============================================================================ % 档位数字与算珠位置对照属性表常量(内珠和外珠) % ============================================================================ % {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}} } % 设置图层 \clist_set:Nn \l_draw_layers_clist { main, frame, mark } % 空图层,避免出现图层收集错误 \draw_layer_begin:n { frame } \draw_layer_end: \draw_layer_begin:n { mark } \draw_layer_end: % ============================================================================ % 尺寸计算函数 % ============================================================================ \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 } % ---- 档杆支杆预计算尺寸 ---- % clip 边界 \dim_set:Nn \l__suanpan_support_clip_lower_dim { \l__suanpan_frame_lower_outer_offset_dim + \l__suanpan_frame_outer_linewidth_dim / 2 } \dim_set:Nn \l__suanpan_support_clip_upper_dim { \l__suanpan_frame_upper_outer_offset_dim - \l__suanpan_frame_outer_linewidth_dim / 2 } % 横梁边线 yshift \dim_set:Nn \l__suanpan_support_beam_yshift_dim { \l__suanpan_frame_b_half_dim - \l__suanpan_frame_inner_linewidth_dim / 2 } % 上梁内线 yshift \dim_set:Nn \l__suanpan_support_upper_inner_yshift_dim { \l__suanpan_frame_upper_inner_offset_dim + \l__suanpan_frame_inner_linewidth_dim / 2 } % 上梁外线 yshift \dim_set:Nn \l__suanpan_support_upper_outer_yshift_dim { \l__suanpan_support_upper_inner_yshift_dim + \l__suanpan_frame_b_half_dim } % 下梁内线 yshift \dim_set:Nn \l__suanpan_support_lower_inner_yshift_dim { \l__suanpan_frame_lower_inner_offset_dim - \l__suanpan_frame_inner_linewidth_dim / 2 } % 下梁外线 yshift \dim_set:Nn \l__suanpan_support_lower_outer_yshift_dim { \l__suanpan_support_lower_inner_yshift_dim - \l__suanpan_frame_b_half_dim } % 档杆延长(消除间隙) \dim_set:Nn \l__suanpan_support_rod_lower_top_extend_dim { \l__suanpan_rod_lower_dim + \l__suanpan_frame_inner_linewidth_dim / 2 } \dim_set:Nn \l__suanpan_support_rod_lower_bottom_extend_dim { \l__suanpan_rod_lower_offset_dim - \l__suanpan_frame_inner_linewidth_dim / 2 } \dim_set:Nn \l__suanpan_support_rod_upper_top_extend_dim { \l__suanpan_rod_upper_dim + \l__suanpan_frame_inner_linewidth_dim / 2 } \dim_set:Nn \l__suanpan_support_rod_upper_bottom_extend_dim { \l__suanpan_rod_upper_offset_dim - \l__suanpan_frame_inner_linewidth_dim / 2 } } % ============================================================================ % 选项处理 % ============================================================================ \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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, empty .default:n = true, empty .initial:n = true, 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 * 3.5 } } } \dim_set:Nn \l__suanpan_rod_linewidth_dim { \fp_to_dim:n { \fp_eval:n { \l__suanpan_frame_inner_linewidth_dim * 0.1 } } } \dim_set:Nn \l__suanpan_bead_linewidth_dim { \fp_to_dim:n { \fp_eval:n { \l__suanpan_frame_inner_linewidth_dim * 0.1 } } } \__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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, linewd .initial:n = 1.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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, beadh .initial:n = 6mm, % 算珠直径 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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, beadd .initial:n = 11.5mm, % 档杆直径(应小于算珠直径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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, rodd .initial:n = 3.5mm, % 边框宽度 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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, framew .initial:n = 6.5mm, % 档间间距 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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, rodsep .initial:n = 1.5pt, % 算珠间距 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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, beadsep .initial:n = 0.9pt, % 缩放参数 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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, % 横梁标记字符格式 font .tl_set:N = \l__suanpan_mark_font_tl, font .initial:n = \bfseries\small, % 算珠高光点位置(角度) 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 \bool_gset_true:N \g__suanpan_support_unit_dirty_bool }, beadspot .initial:n = 0, unknown .code:n = { \__suanpan_error:n { unknown-option } } } % 选项默认值 \keys_set:nn { suanpan } { linewd = 1.0pt, rodsep = 1.5pt, beadsep = 0.9pt, scale = 1.0, 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 = {% 正常内珠样式(带高光圆角矩形) shade, ball~color = innerfillcolor, shading~angle = \l__suanpan_spot_angle_int, draw = innerdrawcolor, 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, } } \hcoffin_gset:Nn \g__suanpan_inner_bead_coffin { \bool_if:NTF \l__suanpan_draft_bool { \begin{tikzpicture} % 无高光圆帽线段算珠 \path[draftline] (-\l_tmpa_dim, 0) -- (\l_tmpa_dim, 0); \end{tikzpicture} }{ \begin{tikzpicture} \path[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} } } } % 构建外珠(远离横梁,不参与计数)容器(盒子) \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, shade, ball~color = outerfillcolor, shading~angle = \l__suanpan_spot_angle_int, draw = outerdrawcolor, 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, } } \hcoffin_gset:Nn \g__suanpan_outer_bead_coffin { \bool_if:NTF \l__suanpan_draft_bool { \begin{tikzpicture} % 无高光圆帽线段算珠 \path[draftline] (-\l_tmpa_dim, 0) -- (\l_tmpa_dim, 0); \end{tikzpicture} }{ \begin{tikzpicture} \path[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} } } } % 构建浮珠容器(盒子) % #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 }{% 正常外珠样式(带高光圆角矩形) shade, draw = #1, 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, } } \hcoffin_gset:Nn \g__suanpan_float_bead_coffin { \bool_if:NTF \l__suanpan_draft_bool { \begin{tikzpicture} \path[draw=#2, draftline] (-\l_tmpa_dim, 0) -- ( \l_tmpa_dim, 0); \end{tikzpicture} }{ \begin{tikzpicture} \path[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} } } } % ============================================================================ % 构建档杆支杆 % ============================================================================ \cs_new:Nn \__suanpan_support_construct: { % TikZ绘图样式 \bool_if:NTF \l__suanpan_draft_bool { % 草稿模式 \tikzset{% rod/.style = {% 档杆绘制样式(不带高光矩形) draw = roddrawcolor, line~width = \l__suanpan_rod_linewidth_dim, }, beam/.style = {% 横梁绘制样式(不带高光) fill = framecolor!20, }, } }{ \tikzset{% rod/.style = {% 档干绘制样式(带高光矩形) shade, left~color = rodfillcolor!95, right~color = rodfillcolor!95, middle~color = rodfillcolor!15, shading~angle = 90 + \l__suanpan_spot_angle_int, draw = roddrawcolor, line~width = \l__suanpan_rod_linewidth_dim, }, beam/.style = {% 横梁绘制样式(带高光矩形) shade, bottom~color = framecolor!60, top~color = framecolor!60, middle~color = framecolor!5, shading~angle = \l__suanpan_spot_angle_int, }, } } \tikzset{% frameinner/.style = {% 边框内线绘制样式 draw = framecolor, line~cap = rect, % 弥补拼接间隙 line~width = \l__suanpan_frame_inner_linewidth_dim, }, frameouter/.style = {% 边框外线绘制样式 draw = framecolor, line~cap = rect, % 弥补拼接间隙 line~width = \l__suanpan_frame_outer_linewidth_dim, }, } \hcoffin_gset:Nn \g__suanpan_support_coffin { \begin{tikzpicture} \clip (-\l__suanpan_support_d_half_dim, \l__suanpan_support_clip_lower_dim) rectangle ( \l__suanpan_support_d_half_dim, \l__suanpan_support_clip_upper_dim); % 下杆 \path[rod] (-\l__suanpan_rod_d_half_dim, \l__suanpan_support_rod_lower_bottom_extend_dim) rectangle ( \l__suanpan_rod_d_half_dim, \l__suanpan_support_rod_lower_top_extend_dim); % 上杆 \path[rod] (-\l__suanpan_rod_d_half_dim, \l__suanpan_support_rod_upper_bottom_extend_dim) rectangle ( \l__suanpan_rod_d_half_dim, \l__suanpan_support_rod_upper_top_extend_dim); \bool_if:NF \g__suanpan_has_frame_bool { % 横梁 \path[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); % 横梁上边线 \path[frameinner, yshift = \l__suanpan_support_beam_yshift_dim] (-\l__suanpan_support_d_half_dim, 0pt) -- ( \l__suanpan_support_d_half_dim, 0pt); % 横梁下边线 \path[frameinner, yshift = -\l__suanpan_support_beam_yshift_dim] (-\l__suanpan_support_d_half_dim, 0pt) -- ( \l__suanpan_support_d_half_dim, 0pt); % 上梁下边线 \path[frameinner, yshift = \l__suanpan_support_upper_inner_yshift_dim] (-\l__suanpan_support_d_half_dim, 0pt) -- ( \l__suanpan_support_d_half_dim, 0pt); % 上梁上边线 \path[frameouter, yshift = \l__suanpan_support_upper_outer_yshift_dim] (-\l__suanpan_support_d_half_dim, 0pt) -- (\l__suanpan_support_d_half_dim, 0pt); % 下梁上边线 \path[frameinner, yshift = \l__suanpan_support_lower_inner_yshift_dim] (-\l__suanpan_support_d_half_dim, 0pt) -- ( \l__suanpan_support_d_half_dim, 0pt); % 下梁下边线 \path[frameouter, yshift = \l__suanpan_support_lower_outer_yshift_dim] (-\l__suanpan_support_d_half_dim, 0pt) -- ( \l__suanpan_support_d_half_dim, 0pt); } \end{tikzpicture} } } % ============================================================================ % 绘制计位点 % ============================================================================ \cs_new:Nn \__suanpan_unit_mark_construct: { % TikZ绘制样式 \bool_if:NTF \l__suanpan_draft_bool { % 草稿模式 \tikzset{% unitmark/.style = {% 计位点(不带高光) fill = black, line~width = \l__suanpan_frame_inner_linewidth_dim, }, } }{ % 正常模式 \tikzset{% unitmark/.style = {% 计位点(带高光) shade, ball~color = black, shading~angle = \l__suanpan_spot_angle_int, line~width = \l__suanpan_bead_linewidth_dim, }, } } \hcoffin_gset:Nn \g__suanpan_unit_mark_coffin { \begin{tikzpicture} \path[unitmark] (0pt, 0pt) circle [radius = \l__suanpan_frame_unit_r_dim]; \end{tikzpicture} } } % ============================================================================ % 构建档位值为0-20的21个档杆总成容器(盒子) % ============================================================================ \cs_new:Nn \__suanpan_support_unit_construct: { \int_step_inline:nnn { 0 }{ 20 } { % 利用\int_to_alph:n { ##1 + 1 }将数字转换成小写字母 \hcoffin_gset:cn { g__suanpan_support_unit_ \int_to_alph:n { ##1 + 1 } _coffin } { \begin{tikzpicture} \begin{scope}[inner~sep=0pt, outer~sep=0pt, anchor=center] % 布置支架 \node[yshift = -\l__suanpan_support_y_offset_dim] at (0, 0) {% \coffin_typeset:Nnnnn \g__suanpan_support_coffin { l } { vc } { 0pt } { 0pt }% }; % 布置算珠 \int_compare:nNnTF { ##1 } = { 0 } { \bool_if:NT \l__suanpan_empty_rod_bool { \__suanpan_draw_beads:n { ##1 } } }{ \__suanpan_draw_beads:n { ##1 } } \end{scope} \end{tikzpicture} } } } % ============================================================================ % 布置算珠 % ============================================================================ % 在垂直方向指定位置布置单个算珠 % #1: 算珠位置编号 % #2: 算珠类型 (0——外珠,1——内珠) \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) } } % 选择并布置算珠基础组件(coffin) \int_case:nnF { #2 } { { 0 } { % 外珠 \node[yshift = \l_tmpb_dim] at (0, 0){% \coffin_typeset:Nnnnn \g__suanpan_outer_bead_coffin { l } { vc } { 0pt } { 0pt }% }; } { 1 } { % 内珠 \node[yshift = \l_tmpb_dim] at (0, 0) {% \coffin_typeset:Nnnnn \g__suanpan_inner_bead_coffin { l } { vc } { 0pt } { 0pt }% }; } }{ \__suanpan_error:nn { bead-type-error } { #2 } } } % 在垂直方向指定位置布置单个算珠 % #1: 算珠位置编号 \cs_new:Npn \__suanpan_draw_single_float_bead:n #1 { % 确定垂直距离 \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) \draw_coffin_use:Nnn \g__suanpan_float_bead_coffin { hc } { vc } \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:cnnn { g__suanpan_support_unit_ \int_to_alph:n { #2 + 1 } _coffin } { hc } { vc } { 0pt, -\l__suanpan_support_y_offset_dim } % 布置计位点标记 \bool_if:NT \l__suanpan_support_unit_bool { \draw_layer_begin:n { mark } \draw_coffin_use:Nnn \g__suanpan_unit_mark_coffin { hc } { vc } \draw_layer_end: } \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_float_bead:n { #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_layer_begin:n { mark } \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: \draw_layer_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绘制样式 \bool_if:NTF \l__suanpan_draft_bool { % 草稿模式 \tikzset{% hframe/.style = {% 水平梁(上下梁,不带高光) draw = framecolor, fill = framecolor!20, line~width = \l__suanpan_frame_inner_linewidth_dim, }, vframe/.style = {% 水平梁(上下梁,不带高光) draw = framecolor, fill = framecolor!20, line~width = \l__suanpan_frame_inner_linewidth_dim, }, } }{ % 正常模式 \tikzset{% hframe/.style = {% 水平梁(上下梁,带高光) shade, top~color = framecolor!95, bottom~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 = {% 垂直梁(左右梁) shade, left~color = framecolor!95, right~color = framecolor!95, middle~color = framecolor!15, shading~angle = 90 + \l__suanpan_spot_angle_int, draw = framecolor, line~width = \l__suanpan_frame_inner_linewidth_dim, }, } } \tikzset{% 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) } } } % 减半档宽度 \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} } } % 上下边框基础容器(盒子) \hcoffin_set:Nn \l_tmpa_coffin { \begin{tikzpicture} \path[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 { \begin{tikzpicture} \path[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; \path[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 ); \path[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 { \begin{tikzpicture} \path[hframe] ( 0pt, \l__suanpan_frame_inner_linewidth_dim / 2 ) -- ++( \l_tmpa_dim, \l__suanpan_frame_inner_linewidth_dim / 2) -- ++( \l__suanpan_frame_b_dim / 3, \l__suanpan_frame_b_dim / 2 - \l__suanpan_frame_inner_linewidth_dim / 2) -- ++(-\l__suanpan_frame_b_dim / 3, \l__suanpan_frame_b_dim / 2 - \l__suanpan_frame_inner_linewidth_dim / 2 ) -- ++(-\l_tmpa_dim, 0 ) -- ++(-\l__suanpan_frame_b_dim / 3, -\l__suanpan_frame_b_dim / 2 - \l__suanpan_frame_inner_linewidth_dim / 2 ) -- cycle; \end{tikzpicture} } \draw_layer_begin:n { frame } % 布置上边框 \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_layer_end: % 状态恢复 \bool_gset_false:N \g__suanpan_has_frame_bool \bool_gset_true:N \g__suanpan_support_dirty_bool \bool_gset_true:N \g__suanpan_support_unit_dirty_bool } % ============================================================================ % 解析档位数字列表(支持区间语法) % #1 输出的 clist 变量(解析结果添加到此 clist 中) % #2 原始数据(可能包含区间数据,如"4-7"或重复"2[3]") % ============================================================================ \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 { - } { % 区间语法,如"4-7" \__suanpan_parse_rod_range:Nx #1 { \l_tmpa_tl } } { \tl_if_in:NnTF \l_tmpa_tl { [ } { % 重复语法,如"2[3]" \__suanpan_parse_rod_repeat: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 { % 验证语法格式 \tl_if_regex_match:nnTF { #2 } { \A \d+ - \d+ (?: - \d+ )? \Z } { % 正则解析提取数字,如"4-7"提取为 { "4", "7" } % 如"4-7-2"提取为 { "4", "7", "2" } \regex_extract_all:nnN { \d+ } { #2 } \l_tmpa_seq \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } % start \tl_set:Nx \l_tmpb_tl { \seq_item:Nn \l_tmpa_seq { 2 } } % end \int_set:Nn \l_tmpa_int { \l_tmpa_tl } \int_set:Nn \l_tmpb_int { \l_tmpb_tl } % 检查是否有第三个数字(step步长) \int_compare:nNnTF { \seq_count:N \l_tmpa_seq } = { 3 } { % 有步长 \tl_set:Nx \l_tmpc_tl { \seq_item:Nn \l_tmpa_seq { 3 } } \int_set:Nn \l_tmpc_int { \l_tmpc_tl } % 校验数据 \__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 } } }{% 无步长,步长为 1 % 校验数据 \__suanpan_check_range:VV \l_tmpa_int \l_tmpb_int % 添加档位值 \int_step_inline:nnn { \l_tmpa_int } { \l_tmpb_int } { \clist_put_right:Nn #1 { ##1 } } } }{ \__suanpan_error:nn { rods-range-syntax-error } { #2 } } } \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 } > { 20 } { \__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 } > { 20 } { \__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 } % ============================================================================ % 解析重复语法"value[count]"(正则法) % #1 输出的 clist 变量 % #2 重复字符串(如"2[3]") % ============================================================================ \cs_new:Npn \__suanpan_parse_rod_repeat:Nn #1#2 { % 使用正则验证格式 \tl_if_regex_match:nnTF { #2 } { \A \d+ \[ \d+ \] \Z } { % 正则解析提取数字,如"2[3]"提取为: {"2", "3" } \regex_extract_all:nnN { \d+ } { #2 } \l_tmpa_seq % 取得档位值 \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } % 取得重复次数 \tl_set:Nx \l_tmpb_tl { \seq_item:Nn \l_tmpa_seq { 2 } } % 转换为整数 \int_set:Nn \l_tmpa_int { \l_tmpa_tl } \int_set:Nn \l_tmpb_int { \l_tmpb_tl } % 校验档位值 \__suanpan_check_rod_value:n { \l_tmpa_int } % 校验重复次数 \int_compare:nNnT { \l_tmpb_int } < { 1 } { \__suanpan_error:nn { repeat-value-error } { \l_tmpb_int } } % 重复添加 \prg_replicate:nn { \l_tmpb_int } { \clist_put_right:NV #1 \l_tmpa_int } } { \__suanpan_error:nn { rods-repeat-syntax-error } { #2 } } } \cs_generate_variant:Nn \__suanpan_parse_rod_repeat:Nn { Nx } % ============================================================================ % 解析算珠位置列表(支持区间语法"4-7" 表示"4,5,6,7") % #1 输出的 clist 变量(解析结果添加到此 clist 中) % #2 原始数据(可能包含区间,如"1,3-5,7") % ============================================================================ \cs_new:Npn \__suanpan_parse_bead_pos_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_bead_pos_range:Nx #1 { \l_tmpa_tl } } { \clist_put_right:NV #1 \l_tmpa_tl } } } % ============================================================================ % 解析算珠位置区间"start-end"(步长为1,正则法) % #1 输出的 clist 变量 % #2 区间字符串(如"4-7") % ============================================================================ \cs_new:Npn \__suanpan_parse_bead_pos_range:Nn #1#2 { % 验证语法格式 \tl_if_regex_match:nnTF { #2 } { \A \d+ - \d+ \Z } { % 正则解析提取数字,如"4-7"提取为: { "4", "7" } \regex_extract_all:nnN { \d+ } { #2 } \l_tmpa_seq \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } } % start \tl_set:Nx \l_tmpb_tl { \seq_item:Nn \l_tmpa_seq { 2 } } % end \int_set:Nn \l_tmpa_int { \l_tmpa_tl } \int_set:Nn \l_tmpb_int { \l_tmpb_tl } % 范围检查(1-11) \__suanpan_check_bead_range:VV \l_tmpa_int \l_tmpb_int \int_step_inline:nnn { \l_tmpa_int } { \l_tmpb_int } { \clist_put_right:Nn #1 { ##1 } } }{ \__suanpan_error:nn { bead-range-syntax-error } { #2 } } } \cs_generate_variant:Nn \__suanpan_parse_bead_pos_range:Nn { Nx } % ============================================================================ % 算珠位置范围检查函数(1-11) % #1 start % #2 end % ============================================================================ \cs_new:Npn \__suanpan_check_bead_range:nn #1#2 { \int_compare:nNnT { #1 } < { 1 } { \__suanpan_error:nn { bead-position-error } { #1 } } \int_compare:nNnT { #2 } > { 11 } { \__suanpan_error:nn { bead-position-error } { #2 } } \int_compare:nNnT { #1 } > { #2 } { \__suanpan_error:nnn { bead-range-error } { #1 } { #2 } } } \cs_generate_variant:Nn \__suanpan_check_bead_range:nn { VV } % ============================================================================ % 用户接口环境 % ============================================================================ % 算盘排版环境 % #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 } % 检测 \l_tmpa_tl 中是否包含控制序列 \mkframe % 支架颜色可能会发生变化,需要重新构建 \tl_if_in:NnT \l_tmpa_tl { \mkframe } { \bool_gset_true:N \g__suanpan_has_frame_bool \bool_gset_true:N \g__suanpan_support_dirty_bool \bool_gset_true:N \g__suanpan_support_unit_dirty_bool } % 按需构建基础容器(盒子) - 仅在第一次和参数变化时重建 % 内珠 \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 } % 0-20档位值的档杆总成 \bool_if:NT \g__suanpan_support_unit_dirty_bool { \__suanpan_support_unit_construct: \bool_gset_false:N \g__suanpan_support_unit_dirty_bool } % 在容器(盒子)中用#2(!b)参数取得的环境内容实现绘制 \hcoffin_set:Nn \l_tmpa_coffin { \draw_begin: % 执行用户内容 \l_tmpa_tl % 检查是否有档位定义 \int_compare:nNnT { \g__suanpan_rod_max_int } = { 0 } { \__suanpan_error:n { rod-count-error } } \draw_end: } }{ % 检查缩放参数 \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 } } % 按用户指定比例缩放 \fp_compare:nNnF { \l__suanpan_scale_fp } = { 1.0 } { \coffin_scale:Nnn \l_tmpa_coffin { \l__suanpan_scale_fp } { \l__suanpan_scale_fp } } % 输出算盘容器(盒子) \coffin_typeset:Nnnnn \l_tmpa_coffin { l } { H } { 0pt } { 0pt } % 状态恢复 \int_gzero:N \g__suanpan_rod_max_int \group_end: } % ============================================================================ % 用户接口命令 % ============================================================================ % 排版算盘一个档位,包括档杆和算珠 % #1 星号命令,绘制计位点(黑色圆点) % #2 档位编号(基于1,从左向右计数) % #3 本档数字(0-20,10-15需用到顶珠和底珠,16-20需要用到悬珠) \NewDocumentCommand{ \rod }{ s m m } { \int_gset:Nn \g__suanpan_rod_max_int { \int_max:nn { \g__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{} } { % 解析档位数字列表(支持区间语法) \__suanpan_parse_rod_list:Nn \l_tmpa_clist { #1 } % 初始化档位计数器 \int_set:Nn \l_tmpa_int { 0 } % 统计档位总数 \int_gset:Nn \g__suanpan_rod_max_int { \clist_count:N \l_tmpa_clist } % 处理计位点标记字符串(忽略所有空格) \str_set:Nn \l_tmpb_str { #2 } \str_remove_all:Nn \l_tmpb_str { ~ } % 获取计位点标记长度 \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_gset:Nn \g__suanpan_rod_max_int { \int_max:nn { \g__suanpan_rod_max_int }{ \l_tmpa_int } } } % 指定某档上某个算珠的颜色 % #1 档位(基于1,从左向右计数) % #2 算珠位置列表(1--11,11为悬珠位置,支持步长为1的区间语法) % #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 } % 解析算珠位置列表(支持步长为1的区间语法) \__suanpan_parse_bead_pos_list: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 { \g__suanpan_rod_max_int } = { 0 } { \__suanpan_error:n { rod-count-error } } \__suanpan_frame_layout:nn { 1 }{ \g__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 } > { \g__suanpan_rod_max_int } { \clist_map_break: } } } % 选项设置用户接口 \NewDocumentCommand{ \suanpanset } { m } { \IfNoValueF { #1 } { \keys_set:nn { suanpan } { #1 } } } \endinput