% % 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-02-15} {v0.2.1} {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