summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2022-07-28 20:11:42 +0000
committerKarl Berry <karl@freefriends.org>2022-07-28 20:11:42 +0000
commitb237c781052da2153e8e120f0e69909e54dad584 (patch)
treecb85963094b12969a2caeb82cc4688abc9028405 /Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty
parentc617f3c41b0840c82ed1f943d62a60cdadb9b2e5 (diff)
exam-zh (28jul22)
git-svn-id: svn://tug.org/texlive/trunk@63995 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty')
-rw-r--r--Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty555
1 files changed, 555 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty b/Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty
new file mode 100644
index 00000000000..6eccb24867a
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/exam-zh/exam-zh-choices.sty
@@ -0,0 +1,555 @@
+%
+% Copyright (c) 2022 Zeping Lee
+% Released under the LaTeX Project Public License v1.3c License.
+% Repository: https://gitee.com/zepinglee/exam-zh
+%
+
+\NeedsTeXFormat{LaTeX2e}
+
+\RequirePackage{expl3}
+\RequirePackage{xparse}
+
+\ProvidesExplPackage {exam-zh-choices} {2022-07-28} {v0.1.13}
+ {exam-zh choices module}
+
+\dim_new:N \l__examzh_choices_column_sep_dim
+\int_new:N \l__examzh_choices_columns_int
+\tl_new:N \l__examzh_choices_label_tl
+\tl_new:N \l__examzh_choices_label_pos_tl
+\tl_new:N \l__examzh_choices_label_align_tl
+\dim_new:N \l__examzh_choices_label_sep_dim
+\dim_new:N \l__examzh_choices_label_width_dim
+\int_new:N \l__examzh_choices_max_columns_int
+
+\keys_define:nn { exam-zh }
+ { choices .meta:nn = { exam-zh / choices } {#1} }
+
+\keys_define:nn { exam-zh / choices }
+ {
+ column-sep .dim_set:N = \l__examzh_choices_column_sep_dim ,
+ columns .int_set:N = \l__examzh_choices_columns_int ,
+ label .tl_set:N = \l__examzh_choices_label_tl ,
+ label-pos .choices:nn =
+ { auto , top-left , left , bottom }
+ { \tl_set_eq:NN \l__examzh_choices_label_pos_tl \l_keys_choice_tl } ,
+ label-align .tl_set:N = \l__examzh_choices_label_align_tl ,
+ label-sep .dim_set:N = \l__examzh_choices_label_sep_dim ,
+ label-width .dim_set:N = \l__examzh_choices_label_width_dim ,
+ max-columns .int_set:N = \l__examzh_choices_max_columns_int ,
+ index .int_set:N = \l__examzh_choices_item_index_int
+ }
+
+\keys_set:nn { exam-zh / choices }
+ {
+ column-sep = 1em ,
+ columns = 0 ,
+ label = \Alph*. ,
+ label-pos = auto ,
+ label-align = right ,
+ label-sep = .5em ,
+ label-width = 0pt ,
+ max-columns = 4 ,
+ index = 1,
+ }
+
+\NewDocumentCommand \setchoices { m }
+ { \keys_set:nn { exam-zh / choices } {#1} }
+
+
+\tl_new:N \l__examzh_choices_counters_tl
+
+\NewDocumentCommand \AddChoicesCounter { m m }
+ % #1: \Alph(用户接口)
+ % #2: \@Alph(具体实现的命令或函数(开发层))
+ {
+ % TODO 这一步的作用是什么,为什么要把函数放在 tl 变量里而不是直接在某处使用?
+ % 猜测:put_right 而不是 set,是为了保证操作 label 的输入值前
+ % 几个函数都被 set
+ \tl_put_right:Nn \l__examzh_choices_counters_tl
+ { \__examzh_choices_process_counter:NN #1 #2 }
+ \cs_set_eq:cN { __examzh_choices_save_ \cs_to_str:N #1 : } #2
+ \cs_set_eq:cN { __examzh_choices_save_ \cs_to_str:N #2 : } #2
+ }
+
+\AddChoicesCounter \arabic \@arabic
+\AddChoicesCounter \alph \@alph
+\AddChoicesCounter \Alph \@Alph
+\AddChoicesCounter \roman \@roman
+\AddChoicesCounter \Roman \@Roman
+
+
+\dim_new:N \l__examzh_choices_total_width_dim
+\seq_new:N \l__examzh_choices_seq
+
+\NewDocumentEnvironment { choices } { O { } +b }
+ {
+ \par \nopagebreak
+ % 严格禁止孤行和寡行
+ \int_set:Nn \clubpenalty { 10000 }
+ \int_set:Nn \widowpenalty { 10000 }
+ % 尽量避免在选项中间换行
+ \int_set:Nn \interlinepenalty { 301 }
+ \noindent
+ \dim_set_eq:NN \l__examzh_choices_total_width_dim \linewidth
+ \int_zero:N \l__examzh_choices_columns_int
+ \dim_zero:N \l__examzh_choices_label_width_dim
+ \keys_set:nn { exam-zh / choices } {#1}
+ }
+ {
+ % 用 \item 分割选项
+ \seq_set_split:Nnn \l__examzh_choices_seq { \item } {#2}
+ % 把第一个空项去掉
+ \seq_if_empty:NF \l__examzh_choices_seq
+ { \seq_pop_left:NN \l__examzh_choices_seq \l_tmpa_tl }
+ % 收集正确的选项
+ \__examzh_choices_collect_correct_choices:N
+ \l__examzh_choices_seq
+ % 计算标签和选项内容的最大自然宽度
+ \__examzh_choices_calc_max_width:N \l__examzh_choices_seq
+ % label-pos = auto 时自动选择标签位置
+ \__examzh_choices_set_auto_label_pos:
+ % 如果用户没有声明列数,计算合适的列数
+ % 「学习点」从默认值是否被改变来测试用户是否输入
+ % 用户没有输入的话就采用自动计算列数
+ \int_compare:nNnT { \l__examzh_choices_columns_int } < {1}
+ { \__examzh_choices_calc_columns: }
+ % 计算每个选项内容的宽度 \l__examzh_choices_item_width_dim
+ \__examzh_choices_calc_item_width:
+ % 输出选项
+ \__examzh_print_choices:N \l__examzh_choices_seq
+ % 输出正确选项
+ \__examzh_print_correctchoice:
+ }
+
+% 用来存正确选项的序号(entry)
+\seq_new:N \l__examzh_choices_correct_choices_label_seq
+% 用来存正确选项的内容
+\seq_new:N \l__examzh_choices_correct_choices_item_seq
+
+% 收集正确的选项,保存在 \l__examzh_choices_correct_choices_seq
+\cs_new:Npn \__examzh_choices_collect_correct_choices:N #1
+ % #1: \l__examzh_choices_seq
+ {
+ \seq_clear:N \l__examzh_choices_correct_choices_label_seq
+ \seq_clear:N \l__examzh_choices_correct_choices_item_seq
+ \seq_clear:N \l_tmpa_seq
+ \seq_map_indexed_inline:Nn #1
+ {
+ % ##1: 选项序号
+ % ##2: 选项内容
+ % 如果分割后,第一个字符是 * 的,表明这是一个正确选项
+ %(即用 \item* 来标记正确答案)
+ \tl_if_head_eq_meaning:nNTF {##2} *
+ {
+ \seq_put_right:Nn \l__examzh_choices_correct_choices_label_seq
+ { \__examzh_choices_correct_choices_label_transfrom:n {##1} }
+ % 将去掉 * 号后的内容保存进 \l_tmpa_tl
+ \tl_set:Nx \l_tmpa_tl { \tl_tail:n {##2} }
+ % 去掉 * 和内容之间的空格
+ \tl_trim_spaces:N \l_tmpa_tl
+ \seq_put_right:NV \l__examzh_choices_correct_choices_item_seq
+ \l_tmpa_tl
+ \seq_put_right:NV \l_tmpa_seq \l_tmpa_tl
+ }
+ {
+ \seq_put_right:Nn \l_tmpa_seq { ##2 }
+ }
+ }
+ \seq_set_eq:NN #1 \l_tmpa_seq
+ }
+
+% 根据 label 的样式 \l__examzh_choices_label_tl 转化正确选项的样式
+\cs_new:Npn \__examzh_choices_correct_choices_label_transfrom:n #1
+ {
+ \group_begin:
+ \int_set:Nn \l__examzh_choices_index_int {#1}
+ % 定义计数器转换函数(如 \Alph 等)
+ \l__examzh_choices_counters_tl
+ % 输出
+ \l__examzh_choices_label_tl
+ \group_end:
+ }
+% 输出正确的选项
+\cs_new:Nn \__examzh_print_correctchoice:
+ {
+ \seq_if_empty:NF \l__examzh_choices_correct_choices_item_seq
+ {
+ \par
+ 参考答案:
+ \seq_use:Nn \l__examzh_choices_correct_choices_label_seq {,~}
+ }
+ }
+
+
+\dim_new:N \l__examzh_choices_item_width_dim
+\dim_new:N \l__examzh_choices_item_min_height_dim
+
+% 计算标签和选项内容的最大宽度,
+% 分别保存到 \l__examzh_choices_label_width_dim 和 \l__examzh_choices_item_width_dim
+% #1: \l__examzh_choices_seq
+\cs_new:Npn \__examzh_choices_calc_max_width:N #1
+ {
+ % 下面这两个的想法是 xchoices 项目可以优化学习的地方
+ % 因为 xchoices 是把变量先设置为第一项的参数,然后让后面的和前面的比
+ % 这里相当于把“设置为第一项的参数”这一步,用默认的“端点量”来代替
+ % 比如取最大的,就和 0 比,这样的话其实也会产生变量会变成第一项的参数的结果
+ % 但是两者性质不同,此处处理让 第一项 「没有特殊性」
+ % 后面的计算最小高度的也是如此
+ \dim_zero:N \l__examzh_choices_item_width_dim
+ \dim_set_eq:NN \l__examzh_choices_item_min_height_dim \c_max_dim
+ \seq_map_indexed_inline:Nn #1
+ {
+ % -- 标签 --
+ % 把标签整体放进 \l_tmpa_box
+ \hbox_set:Nn \l_tmpa_box { \__examzh_choices_the_label:n {##1} }
+ % 测量宽度
+ \dim_set:Nn \l_tmpa_dim { \box_wd:N \l_tmpa_box }
+ % 与当前最大值比较,最后效果是 \l__examzh_choices_label_width_dim 储存了所有标签中宽度最大的标签的宽度值
+ \dim_compare:nNnT
+ { \l_tmpa_dim } > { \l__examzh_choices_label_width_dim }
+ { \dim_set_eq:NN \l__examzh_choices_label_width_dim \l_tmpa_dim }
+ % -- 选项内容 --
+ % 把内容放进 \l_tmpa_box 中
+ \hbox_set:Nn \l_tmpa_box {##2}
+ % 测量宽度
+ \dim_set:Nn \l_tmpa_dim { \box_wd:N \l_tmpa_box }
+ % 与当前最大值比较,最后效果是 \l__examzh_choices_item_width_dim 储存了所有选项内容中宽度最大的内容的宽度值
+ \dim_compare:nNnT
+ { \l_tmpa_dim } > { \l__examzh_choices_item_width_dim }
+ {
+ \dim_set_eq:NN \l__examzh_choices_item_width_dim
+ \l_tmpa_dim
+ }
+ % -- 找到最小高度 --
+ % 把内容高度储存到 \l_tmpb_dim
+ \dim_set:Nn \l_tmpb_dim { \box_ht:N \l_tmpa_box }
+ % 与当前最小值比较,最后效果是 \l__examzh_choices_item_min_height_dim 储存了所有内容中高度最小的内容的高度值
+ \dim_compare:nNnT
+ { \l_tmpb_dim } < { \l__examzh_choices_item_min_height_dim }
+ { \dim_set_eq:NN \l__examzh_choices_item_min_height_dim \l_tmpb_dim }
+ \box_clear:N \l_tmpa_box
+ }
+ }
+
+% TODO 没看懂怎么实现的
+\int_new:N \l__examzh_choices_index_int
+
+% \Alph* 形式生成正确的标签
+\cs_new:Npn \__examzh_choices_the_label:n #1
+ {
+ \group_begin:
+ \int_set:Nn \l__examzh_choices_index_int
+ {
+ \int_eval:n
+ {
+ \l__examzh_choices_item_index_int + #1 - 1
+ }
+ }
+ \l__examzh_choices_counters_tl
+ \l__examzh_choices_label_tl
+ \group_end:
+ }
+
+\cs_new:Npn \__examzh_choices_process_counter:NN #1#2
+ % #1: \Alph
+ % #2: \@Alph
+ {
+ % 用户可以同时使用 #1 和 #2 两个函数(命令)作为 label 的操作函数
+ % #1 的内核原理函数是 #2
+ \cs_set:Npn #1 { \__examzh_choices_process_counter_aux:Nn #2 }
+ \cs_set:Npn #2 { \__examzh_choices_process_counter_aux:Nn #2 }
+ }
+
+\cs_new:Npn \__examzh_choices_process_counter_aux:Nn #1#2
+ % #1: \@Alph
+ {
+ \tl_if_eq:nnTF {#2} { * }
+ {
+ % 如果是 \alph* 类型的,效果为 \alph{ \l__examzh_choices_index_int }
+ \use:c { __examzh_choices_save_ \cs_to_str:N #1 : }
+ { \l__examzh_choices_index_int }
+ }
+ {
+ % 否则就是 \alph{...} 效果
+ \use:c { __examzh_choices_save_ \cs_to_str:N #1 : } {#2}
+ }
+ }
+
+
+% 超过这一高度阈值的选项视为插图模式
+% 注意使用 tl
+% TODO 为何要使用 tl 而不用 dim ?
+\tl_new:N \l__examzh_choices_figure_mode_threshold_tl
+\tl_set:Nn \l__examzh_choices_figure_mode_threshold_tl { 2 \baselineskip }
+
+\cs_new:Npn \__examzh_choices_set_auto_label_pos:
+ {
+ \tl_if_eq:NnT \l__examzh_choices_label_pos_tl { auto }
+ {
+ % 若最小高度超过阈值,推测其中包含插图,将标签位置改为左居中
+ \dim_compare:nNnTF
+ { \l__examzh_choices_item_min_height_dim } >
+ { \l__examzh_choices_figure_mode_threshold_tl }
+ { \tl_set:Nn \l__examzh_choices_label_pos_tl { left } }
+ { \tl_set:Nn \l__examzh_choices_label_pos_tl { top-left } }
+ }
+ }
+
+
+\int_new:N \l__examzh_tmp_int
+
+% 计算选项的合适列数,存到 \l__examzh_choices_columns_int
+\cs_new:Npn \__examzh_choices_calc_columns:
+ {
+ % 若标签不在底部,将 label-width 和 label-sep 加到 \l__examzh_choices_item_width_dim 里面
+ \tl_if_eq:NnF \l__examzh_choices_label_pos_tl { bottom }
+ {
+ \dim_add:Nn \l__examzh_choices_item_width_dim
+ { \l__examzh_choices_label_width_dim + \l__examzh_choices_label_sep_dim }
+ }
+ % [总宽度 / 最大的选项宽度] = 列数
+ % 这个计算出来是作为「算出来的、可以排的最大列数」
+ % 将要和下面的手动(或者默认的)最大列数进行比较,来确定最后排多少列
+ \int_set:Nn \l__examzh_choices_columns_int
+ {
+ \int_div_truncate:nn
+ { \l__examzh_choices_total_width_dim + \l__examzh_choices_column_sep_dim }
+ { \l__examzh_choices_item_width_dim + \l__examzh_choices_column_sep_dim }
+ }
+ % 如果上面的计算算出来是 0 的话,就设置为 1
+ \int_compare:nNnTF { \l__examzh_choices_columns_int } = {0}
+ { \int_set:Nn \l__examzh_choices_columns_int {1} }
+ % 从允许的最大列数开始,每次除以 2,直到行宽允许排下
+ % 比如设置了最大列数是 4 , 但是算出来可以排 5
+ % 那么就会将 [4 / 2] = 2 < 5 作为列数
+ \int_set_eq:NN \l__examzh_tmp_int \l__examzh_choices_max_columns_int
+ \int_while_do:nNnn
+ { \l__examzh_tmp_int } > { \l__examzh_choices_columns_int }
+ {
+ \int_set:Nn \l__examzh_tmp_int
+ { \int_div_truncate:nn { \l__examzh_tmp_int } {2} }
+ }
+ \int_set_eq:NN \l__examzh_choices_columns_int \l__examzh_tmp_int
+ }
+
+
+% 计算选项的最终宽度,保存到 \l__examzh_choices_item_width_dim
+\cs_new:Npn \__examzh_choices_calc_item_width:
+ {
+ \dim_set:Nn \l__examzh_choices_item_width_dim
+ {
+ % TODO 不是很理解这里的算法
+ ( \l__examzh_choices_total_width_dim
+ - \l__examzh_choices_columns_int \l__examzh_choices_column_sep_dim
+ + \l__examzh_choices_column_sep_dim
+ ) / \l__examzh_choices_columns_int
+ }
+ % 若标签不在底部,将 label-width 和 label-sep 算进来
+ % TODO 算进来?那怎么还是 sub?
+ \tl_if_eq:NnF \l__examzh_choices_label_pos_tl { bottom }
+ {
+ \dim_sub:Nn \l__examzh_choices_item_width_dim
+ { \l__examzh_choices_label_width_dim + \l__examzh_choices_label_sep_dim }
+ }
+ }
+
+
+\int_new:N \l__examzh_choices_current_col_int
+
+% #1: \l__examzh_choices_seq
+\cs_new:Npn \__examzh_print_choices:N #1
+ {
+ \int_zero:N \l__examzh_choices_current_col_int
+ \seq_map_indexed_inline:Nn \l__examzh_choices_seq
+ {
+ \int_incr:N \l__examzh_choices_current_col_int
+ % 当前列号重置为 1
+ \int_compare:nNnT
+ { \l__examzh_choices_current_col_int } > { \l__examzh_choices_columns_int }
+ {
+ % \par \noindent
+ \newline
+ \int_set:Nn \l__examzh_choices_current_col_int {1}
+ }
+ % TODO 为什么 > 1 才加呢? 这样的话第 1 列和第 2 列之间就没有这个间距?
+ \int_compare:nNnT { \l__examzh_choices_current_col_int } > {1}
+ {
+ \skip_horizontal:N \l__examzh_choices_column_sep_dim
+ % 增加一点弹性
+ \skip_horizontal:n {0pt plus 1pt minus 1pt}
+ }
+ \__examzh_print_single_choice:nn {##1} {##2}
+ }
+ \par
+ }
+
+
+\coffin_new:N \l__examzh_choices_item_coffin
+\coffin_new:N \l__examzh_choices_label_coffin
+
+\cs_new:Npn \__examzh_print_single_choice:nn #1#2
+ {
+ % 选项标签
+ \__examzh_choices_make_label_coffin:n {#1}
+ % 选项内容
+ \__examzh_choices_make_item_coffin:n {#2}
+ % 合并选项的标签和内容
+ \str_case:Vn \l__examzh_choices_label_pos_tl
+ {
+ { top-left }
+ {
+ \coffin_join:NnnNnnnn
+ \l__examzh_choices_item_coffin {l} {H}
+ \l__examzh_choices_label_coffin {r} {H}
+ { - \l__examzh_choices_label_sep_dim }
+ { 0pt }
+ }
+ { left }
+ {
+ \coffin_join:NnnNnnnn
+ \l__examzh_choices_item_coffin {l} {vc}
+ \l__examzh_choices_label_coffin {r} {vc}
+ { - \l__examzh_choices_label_sep_dim }
+ { 0pt }
+ }
+ { bottom }
+ {
+ \coffin_join:NnnNnnnn
+ \l__examzh_choices_item_coffin {hc} {b}
+ \l__examzh_choices_label_coffin {hc} {t}
+ { 0pt }
+ % { - \l__examzh_choices_label_sep_dim }
+ { 0pt }
+ }
+ }
+ % 输出合并后
+ \coffin_typeset:Nnnnn \l__examzh_choices_item_coffin {l} {H} {0pt} {0pt}
+ \coffin_clear:N \l__examzh_choices_item_coffin
+ \coffin_clear:N \l__examzh_choices_label_coffin
+ }
+
+% 将标签内容存入 coffin
+\cs_new:Npn \__examzh_choices_make_label_coffin:n #1
+ {
+ \hcoffin_set:Nn \l__examzh_choices_label_coffin
+ {
+ \hbox_to_wd:nn { \l__examzh_choices_label_width_dim }
+ { \__examzh_choices_make_label:n {#1} \strut }
+ }
+ }
+
+\cs_new:Npn \__examzh_choices_make_label:n #1
+ {
+ \str_case:Vn \l__examzh_choices_label_align_tl
+ {
+ { left } { \rlap { \__examzh_choices_the_label:n {#1} } \hss }
+ { center } { \hss \clap { \__examzh_choices_the_label:n {#1} } \hss }
+ { right } { \hss \llap { \__examzh_choices_the_label:n {#1} } }
+ }
+ }
+
+\bool_new:N \l__examzh_choices_figure_mode_bool
+
+% 将选项内容存入 coffin
+\cs_new:Npn \__examzh_choices_make_item_coffin:n #1
+ {
+ \hcoffin_set:Nn \l__examzh_choices_item_coffin
+ {
+ % 优先尝试使用 hbox,这是因为在 \vcoffin_set 外部能保留原来的 \linewidth 和
+ % \textwidth,方便用户在 \includegraphics 中使用
+ \hbox_set:Nn \l_tmpa_box {#1}
+ % 若盒子的自然高度大于 2 行,且深度为 0pt,设置为插图模式
+ \bool_lazy_and:nnT
+ {
+ \dim_compare_p:nNn { \box_ht:N \l_tmpa_box } >
+ { \l__examzh_choices_figure_mode_threshold_tl }
+ }
+ { \dim_compare_p:nNn { \box_dp:N \l_tmpa_box } < { 1pt } }
+ { \bool_set_true:N \l__examzh_choices_figure_mode_bool }
+ \vcoffin_set:Nnn \l_tmpa_coffin
+ { \l__examzh_choices_item_width_dim }
+ {
+ \dim_set_eq:NN \parskip \c_zero_dim
+ \dim_set_eq:NN \parindent \listparindent
+ \noindent
+ % \strut
+ % 若标签在底部,将图片居中对齐。
+ \tl_if_eq:NnT \l__examzh_choices_label_pos_tl { bottom }
+ { \centering }
+ \dim_compare:nNnTF
+ { \box_wd:N \l_tmpa_box } > { \l__examzh_choices_item_width_dim }
+ { #1 }
+ { \box_use_drop:N \l_tmpa_box }
+ % 使用 \strut 将行距撑开,防止跟下一行选项的间距过小
+ \mode_if_horizontal:T { \strut }
+ }
+ \dim_set:Nn \l_tmpa_dim { \coffin_ht:N \l_tmpa_coffin }
+ \bool_if:NT \l__examzh_choices_figure_mode_bool
+ {
+ \coffin_set_horizontal_pole:Nnn \l_tmpa_coffin {T}
+ { \l_tmpa_dim - 0.7 \baselineskip }
+ }
+ \coffin_typeset:Nnnnn \l_tmpa_coffin {l} {T} {0pt} {0pt}
+ \coffin_clear:N \l_tmpa_coffin
+ }
+ }
+
+
+% 使用中文字体直接输出 unicode 带圈数字
+% \circlednumber 的参数既可以接受 LaTeX2e 的 <counter>,也可以直接接受 <intexpr>。
+% \NewDocumentCommand \circlednumber { m }
+% {
+% \int_if_exist:cTF { c@ #1 }
+% { \int_set_eq:Nc \l_tmpa_int { c@#1 } }
+% { \int_set:Nn \l_tmpa_int { #1 } }
+% \exp_args:Nx \__examzh_choices_circled_number:n { \int_use:N \l_tmpa_int }
+% }
+
+\cs_new:Npn \__examzh_choices_circled_number:n #1
+ {
+ \int_set:Nn \l_tmpa_int {#1}
+ \int_compare:nNnTF { \l_tmpa_int } = { 0 }
+ { \int_set:Nn \l_tmpa_int { "24EA } }
+ {
+ \int_compare:nNnTF { \l_tmpa_int } < { 21 }
+ { \int_add:Nn \l_tmpa_int { "245F } }
+ {
+ \int_compare:nNnTF { \l_tmpa_int } < { 36 }
+ { \int_add:Nn \l_tmpa_int { "3250 } }
+ {
+ \int_compare:nNnTF { \l_tmpa_int } < { 51 }
+ { \int_add:Nn \l_tmpa_int { "32B0 } }
+ {
+ \msg_error:nnn { exam-zh / choices }
+ { invalid-circled-number } { \int_use:N \l_tmpa_int }
+ }
+ }
+ }
+ }
+ \group_begin:
+ % TODO 为何要用 \CJKfamily+ { }
+ % xeCJK 宏包文档:当 \CJKfamily+ 参数为空时,则使用当前的 CJK 字体族。
+ \CJKfamily+ { }
+ \symbol { \l_tmpa_int }
+ \group_end:
+ }
+
+\msg_new:nnn { exam-zh / choices } { invalid-circled-number }
+ { Invalid~ circled~ number~ #1. }
+
+\AddChoicesCounter \circlednumber \__examzh_choices_circled_number:n
+
+
+% TODO 答案控制
+
+% 选择题答案控制
+% - 直接在后面显示
+% - 在括号内显示
+% - 手动输入
+% - 能否通过写中途文件方式使得答案可以出现在前面的括号内
+% - 统一移动到最后
+% - 也是 choices 的形式
+% - 表格形式
+
+% 答案确定
+% 通过 \item 是否带 * 判断,有的话则标记为正确答案 \ No newline at end of file