26 Star 114 Fork 31

xkwxdyy/exam-zh

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
exam-zh-choices.sty 24.36 KB
一键复制 编辑 原始数据 按行查看 历史
kangweixia_xdyy 提交于 2024-03-23 16:08 . 修改版本号
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
%
% Copyright (c) 2024 Zeping Lee
% Released under the LaTeX Project Public License v1.3c License.
% Repository: https://gitee.com/xkwxdyy/exam-zh
%
\NeedsTeXFormat{LaTeX2e}
\RequirePackage{expl3}
\RequirePackage{xparse}
\ProvidesExplPackage {exam-zh-choices} {2024-03-23} {v0.2.3}
{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,
% 环境上方的额外距离
top-sep .skip_set:N = \l__examzh_choices_top_sep_skip,
% 环境下方的额外距离
bottom-sep .skip_set:N = \l__examzh_choices_bottom_sep_skip,
% 若不是单行排版,则可以控制行之间的额外间距
linesep .skip_set:N = \l__examzh_choices_line_sep_skip
}
\keys_set:nn { exam-zh / choices }
{
column-sep = 1em ,
columns = 0 ,
label = \Alph*. ,
label-pos = auto ,
label-align = left ,
label-sep = .5em ,
label-width = 0pt ,
max-columns = 4 ,
index = 1,
top-sep = 0pt,
bottom-sep = 0pt,
linesep = 0pt plus .5ex
}
\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 }
{
\keys_set:nn { exam-zh / choices } {#1}
\par \nopagebreak
% 严格禁止孤行和寡行
\int_set:Nn \clubpenalty { 10000 }
\int_set:Nn \widowpenalty { 10000 }
% 尽量避免在选项中间换行
\int_set:Nn \interlinepenalty { 301 }
\vspace* { \l__examzh_choices_top_sep_skip }
\noindent
% \dim_set_eq:NN \l__examzh_choices_total_width_dim \linewidth
\dim_set:Nn \l__examzh_choices_total_width_dim { \linewidth - \leftskip - \rightskip }
\int_zero:N \l__examzh_choices_columns_int
\dim_zero:N \l__examzh_choices_label_width_dim
}
{
% 用 \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: }
% 解决 columns 无效的问题
\keys_set:nn { exam-zh / choices } {#1}
% 计算每个选项内容的宽度 \l__examzh_choices_item_width_dim
\__examzh_choices_calc_item_width:
% 输出选项
\__examzh_print_choices:N \l__examzh_choices_seq
% 输出正确选项
% \__examzh_print_correctchoice:
\vspace* { \l__examzh_choices_bottom_sep_skip }
}
% 用来存正确选项的序号(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
\\[ \l__examzh_choices_line_sep_skip ]
% \newline
% \skip_vertical:N \l__examzh_choices_line_sep_skip
\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
% \box_new:N \l__examzh_choices_item_box
% \box_new:N \l__examzh_choices_label_box
\cs_new:Npn \__examzh_print_single_choice:nn #1#2
{
% 选项标签
\__examzh_choices_make_label_coffin:n {#1}
% \__examzh_choices_make_label_box:n {#1}
% 选项内容
\__examzh_choices_make_item_coffin:n {#2}
% \__examzh_choices_make_item_box:n {#2}
% 合并选项的标签和内容
\str_case:Vn \l__examzh_choices_label_pos_tl
{
{ top-left }
{
\coffin_join:NnnNnnnn
\l__examzh_choices_label_coffin {r} {H}
\l__examzh_choices_item_coffin {l} {H}
{ \l__examzh_choices_label_sep_dim }
{ 0pt }
% \hbox_set:Nn \l__examzh_choices_item_box
% {
% \box_use_drop:N \l__examzh_choices_label_box
% \kern \l__examzh_choices_label_sep_dim
% \box_use_drop:N \l__examzh_choices_item_box
% }
}
{ left }
{
\coffin_join:NnnNnnnn
\l__examzh_choices_label_coffin {r} {vc}
\l__examzh_choices_item_coffin {l} {vc}
{ \l__examzh_choices_label_sep_dim }
{ 0pt }
% \hbox_set:Nn \l__examzh_choices_item_box
% {
% \box_move_down:nn
% {
% (
% \box_ht:N \l__examzh_choices_label_box -
% \box_dp:N \l__examzh_choices_label_box -
% \box_ht:N \l__examzh_choices_item_box +
% \box_dp:N \l__examzh_choices_item_box
% ) / 2
% }
% { \box_use_drop:N \l__examzh_choices_label_box }
% \kern \l__examzh_choices_label_sep_dim
% \box_use_drop:N \l__examzh_choices_item_box
% }
}
{ bottom }
{
\coffin_join:NnnNnnnn
\l__examzh_choices_label_coffin {hc} {t}
\l__examzh_choices_item_coffin {hc} {b}
{ 0pt }
% { - \l__examzh_choices_label_sep_dim }
{ 0pt }
% \hbox_set:Nn \l__examzh_choices_item_box
% {
% % \vbox_top:n
% % {
% % \box_use:N \l__examzh_choices_item_box
% % \nointerlineskip
% % % \kern \l__examzh_choices_label_sep_dim
% % \box_move_left:nn
% % {
% % (
% % \box_wd:N \l__examzh_choices_label_box -
% % \box_wd:N \l__examzh_choices_item_box
% % ) / 2
% % }
% % { \box_use_drop:N \l__examzh_choices_label_box }
% % \box_clear:N \l__examzh_choices_item_box
% % }
% \hbox_set:Nn \l__examzh_choices_item_box
% {
% \box_use:N \l__examzh_choices_item_box
% \kern \dim_eval:n
% {
% ( - \box_wd:N \l__examzh_choices_label_box
% - \box_wd:N \l__examzh_choices_item_box ) / 2
% }
% \box_move_down:nn
% {
% \box_ht:N \l__examzh_choices_label_box +
% \box_dp:N \l__examzh_choices_item_box
% % + \l__examzh_choices_label_sep_dim
% }
% { \box_use_drop:N \l__examzh_choices_label_box }
% \box_clear:N \l__examzh_choices_item_box
% }
% }
}
}
% 输出合并后
% \coffin_typeset:Nnnnn \l__examzh_choices_item_coffin {l} {H} {0pt} {0pt}
\coffin_typeset:Nnnnn \l__examzh_choices_label_coffin {l} {H} {0pt} {0pt}
\coffin_clear:N \l__examzh_choices_item_coffin
\coffin_clear:N \l__examzh_choices_label_coffin
% \box_use_drop:N \l__examzh_choices_item_box
}
% 将标签内容存入 coffin
\cs_new:Npn \__examzh_choices_make_label_coffin:n #1
% 将标签内容存入 box
% \cs_new:Npn \__examzh_choices_make_label_box:n #1
{
\hcoffin_set:Nn \l__examzh_choices_label_coffin
% \hbox_set:Nn \l__examzh_choices_label_box
{
\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
% 将选项内容存入 box
% \cs_new:Npn \__examzh_choices_make_item_box:n #1
{
\hcoffin_set:Nn \l__examzh_choices_item_coffin
% \hbox_set:Nn \l__examzh_choices_item_box
{
% 优先尝试使用 hbox,这是因为在 \vbox_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 }
% \vbox_set:Nn \l_tmpa_box
{
% \dim_set_eq:NN \parskip \c_zero_dim
% \dim_set_eq:NN \parindent \listparindent
\dim_set_eq:NN \hsize \l__examzh_choices_item_width_dim
\dim_set_eq:NN \linewidth \hsize
\dim_set_eq:NN \columnwidth \hsize
\dim_set_eq:NN \parskip \c_zero_dim
\dim_set_eq:NN \parindent \listparindent
\dim_set:Nn \leftskip { 0pt }
\dim_set:Nn \rightskip { 0pt }
\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
% \dim_set:Nn \l_tmpa_dim { \box_ht:N \l_tmpa_box }
% \bool_if:NTF \l__examzh_choices_figure_mode_bool
% {
% \box_move_up:nn { \l_tmpa_dim - 0.7 \baselineskip } { \box_use_drop:N \l_tmpa_box }
% }
{
\coffin_set_horizontal_pole:Nnn \l_tmpa_coffin {T}
{ \l_tmpa_dim - 0.7 \baselineskip }
% \vbox_top:n { \vbox_unpack_drop:N \l_tmpa_box }
}
\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 是否带 * 判断,有的话则标记为正确答案
\endinput
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
TeX/LaTeX
1
https://gitee.com/xkwxdyy/exam-zh.git
git@gitee.com:xkwxdyy/exam-zh.git
xkwxdyy
exam-zh
exam-zh
main

搜索帮助