6

Is it possible to perfectly align the baseline of p/bNiceMatrix? (The misalignment is quite obvious when you have a big matrix...)

MWE

\documentclass{article}

\usepackage{nicematrix}

\begin{document}

\begin{equation} \rlap{\rule{57mm}{0.11pt}}0 = \begin{NiceMatrix}[baseline=2] 0 & 1 \ 0 & 1 \ 0 & 1 \ \end{NiceMatrix}\quad 0 = \begin{pNiceMatrix}[baseline=2] 0 & 2 \ 0 & 2 \ 0 & 2 \ \end{pNiceMatrix}\quad 0 = \begin{bNiceMatrix}[baseline=2] 0 & 3 \ 0 & 3 \ 0 & 3 \ \end{bNiceMatrix} \end{equation}

\end{document}

Outcome

enter image description here


Thank you for all the replies.

Update: The patch suggested by @Ruixi Zhang solves the misalignment.

Update 2 (2021-11-26): There seems to be a side effect of the patch.

Test code

\usepackage{booktabs}
\usepackage{nicematrix}
...
\begin{table}[h]
    \caption{A table}
    \centering
    \begin{NiceTabular}{ll}[cell-space-limits=3pt]
        \toprule
        a & b \\
        \midrule
        \Block{}{new\\line} & c \\
        \bottomrule
    \end{NiceTabular}
\end{table}

side effect

yzhang
  • 205
  • 2
    The problem seems to be in the [baseline =...]. In this example, removing [baseline=2] produces the correct alignment while [baseline=1] does not. – Simon Dispa Nov 20 '21 at 14:24
  • (I'm the author of nicematrix). I was not aware of that strange behaviour of the key baseline. At this time, I have no solution. I will investigate :-) – F. Pantigny Nov 20 '21 at 19:34
  • @F.Pantigny I doubt it’s the package’s fault. For odd number of rows, the alignment is already off in array. So anything built upon array will carry this defect. See https://github.com/latex3/latex2e/issues/147 – Ruixi Zhang Nov 20 '21 at 23:13
  • Please try the updated fix :) – Ruixi Zhang Nov 21 '21 at 17:28
  • 1
    I will write a new version of nicematrix to correct that bug. – F. Pantigny Nov 22 '21 at 13:25
  • I'd suggest to post the follow-up newline in \Block problem as a new question. And, I can't reproduce it with nicematrix v6.4 2021/11/23 so a complete example is mostly welcome. – muzimuzhi Z Nov 28 '21 at 13:47

3 Answers3

8

This is a “dual” bug. The first part is in the LaTeX kernel itself and the second part is in the nicematrix package.

  1. The first part of the bug is in the underlying array environment (and it’s been there since LaTeX2.09). I’ve reported this to the LaTeX team over two years ago (https://github.com/latex3/latex2e/issues/147). Any fixes will break backward compatibility, so the fixes are very unlikely to be implemented in the kernel.

    The problem is that each row of an array has a “strut”. This strut has a height-to-depth ratio of 7:3. But it should instead have a height of <half baseline> + <math axis> and a depth of <half baseline> - <math axis>. The difference is very small in most cases, but still visible enough to cause misalignment.

    For example, a 10pt article with 12pt baseline separation, set in Computer Modern Math, should have strut height 8.5pt and strut depth 3.5pt. But LaTeX sets strut height to about 8.4pt and strut depth to about 3.6pt, causing a tiny 0.1/2=0.05pt misalignment.

  2. The second part of the bug is in the environment NiceArrayWithDelims. Even after we fix the kernel strut behavior, there is still a 0.4pt misalignment. The problem seems to be in \vcenter{ \skip_vertical:N -\l_tmpa_dim ... \skip_vertical:N -\l_tmpb_dim }. An offset of \arrayrulewidth seems to be missing.

    There is no easy way to patch LaTeX3 environment, so I will just copy the entire definition of NiceArrayWithDelims below. Only two lines of code are replaced. Thus, this fix is not to be taken seriously. It only serves as a temporary fix.

Complete code:

\documentclass{article}

\usepackage{nicematrix}

% Fix the LaTeX kernel part of the bug \usepackage{etoolbox} \makeatletter \AtBeginDocument{% % nicematrix loads array.sty \patchcmd@array {\arraystretch @tempdima} {\dimexpr\arraystretch\baselineskip/2 +\arraystretch\extrarowheight +\fontdimen22\textfont\tw@\relax} {\typeout{Fixed array strut height for array.sty}} {\typeout{Couldn't patch \string@array}}% \patchcmd@array {\arraystretch \dp \strutbox} {\dimexpr\arraystretch\baselineskip -\arraystretch\baselineskip/2 -\fontdimen22\textfont\tw@\relax} {\typeout{Fixed array strut depth for array.sty}} {\typeout{Couldn't patch \string@array}}% \let@@array@array } \makeatother

% `Fix' the package bug. % Only two lines are replaced!!! % Not to be taken as a serious fix!!! \makeatletter \ExplSyntaxOn \RenewDocumentEnvironment { NiceArrayWithDelims } { m m O { } m ! O { } t \CodeBefore } { \bool_if:NT \c__nicematrix_revtex_bool __nicematrix_patch_for_revtex: __nicematrix_provide_pgfsyspdfmark: \bool_if:NT \c__nicematrix_footnote_bool \savenotes \bgroup \tl_gset:Nn \g__nicematrix_left_delim_tl { #1 } \tl_gset:Nn \g__nicematrix_right_delim_tl { #2 } \tl_gset:Nn \g__nicematrix_preamble_tl { #4 } \int_gzero:N \g__nicematrix_block_box_int \dim_zero:N \g__nicematrix_width_last_col_dim \dim_zero:N \g__nicematrix_width_first_col_dim \bool_gset_false:N \g__nicematrix_row_of_col_done_bool \str_if_empty:NT \g__nicematrix_name_env_str { \str_gset:Nn \g__nicematrix_name_env_str { NiceArrayWithDelims } } __nicematrix_adapt_S_column: \bool_if:NTF \l__nicematrix_NiceTabular_bool \mode_leave_vertical: __nicematrix_test_if_math_mode: \bool_if:NT \l__nicematrix_in_env_bool { __nicematrix_fatal:n { Yet~in~env } } \bool_set_true:N \l__nicematrix_in_env_bool \cs_gset_eq:NN __nicematrix_old_CT@arc@ \CT@arc@ \cs_if_exist:NT \tikz@library@external@loaded { \tikzexternaldisable \cs_if_exist:NT \ifstandalone { \tikzset { external / optimize = false } } } \int_gincr:N \g__nicematrix_env_int \bool_if:NF \l__nicematrix_block_auto_columns_width_bool { \dim_gzero_new:N \g__nicematrix_max_cell_width_dim } \seq_gclear:N \g__nicematrix_blocks_seq \seq_gclear:N \g__nicematrix_pos_of_blocks_seq \seq_gclear:N \g__nicematrix_pos_of_stroken_blocks_seq \seq_gclear:N \g__nicematrix_pos_of_xdots_seq \tl_gclear_new:N \g__nicematrix_code_before_tl \tl_gclear:N \g__nicematrix_row_style_tl \bool_gset_false:N \g__nicematrix_aux_found_bool \tl_if_exist:cT { c__nicematrix _ \int_use:N \g__nicematrix_env_int _ tl } { \bool_gset_true:N \g__nicematrix_aux_found_bool \use:c { c__nicematrix _ \int_use:N \g__nicematrix_env_int _ tl } } \tl_gclear:N \g__nicematrix_aux_tl \tl_if_empty:NF \g__nicematrix_code_before_tl { \bool_set_true:N \l__nicematrix_code_before_bool \tl_put_right:NV \l__nicematrix_code_before_tl \g__nicematrix_code_before_tl } \bool_if:NTF \l__nicematrix_NiceArray_bool { \keys_set:nn { NiceMatrix / NiceArray } } { \keys_set:nn { NiceMatrix / pNiceArray } } { #3 , #5 } \tl_if_empty:NF \l__nicematrix_rules_color_tl { \exp_after:wN __nicematrix_set_CT@arc@: \l__nicematrix_rules_color_tl \q_stop } \IfBooleanTF { #6 } __nicematrix_pre_array_i:w __nicematrix_pre_array: } { \bool_if:NTF \l__nicematrix_light_syntax_bool { \use:c { end __nicematrix-light-syntax } } { \use:c { end __nicematrix-normal-syntax } } \c_math_toggle_token \skip_horizontal:N \l__nicematrix_right_margin_dim \skip_horizontal:N \l__nicematrix_extra_right_margin_dim \hbox_set_end: \bool_if:NT \l__nicematrix_width_used_bool { \int_compare:nNnT \g__nicematrix_total_X_weight_int = 0 { __nicematrix_error:n { width~without~X~columns } } } \int_compare:nNnT \g__nicematrix_total_X_weight_int > 0 { \tl_gput_right:Nx \g__nicematrix_aux_tl { \bool_set_true:N \l__nicematrix_X_columns_aux_bool \dim_set:Nn \l__nicematrix_X_columns_dim { \dim_compare:nNnTF { \dim_abs:n { \l__nicematrix_width_dim - \box_wd:N \l__nicematrix_the_array_box } } < { 0.001 pt } { \dim_use:N \l__nicematrix_X_columns_dim } { \dim_eval:n { ( \l__nicematrix_width_dim - \box_wd:N \l__nicematrix_the_array_box ) / \int_use:N \g__nicematrix_total_X_weight_int + \l__nicematrix_X_columns_dim } } } } } \int_compare:nNnT \l__nicematrix_last_row_int > { -2 } { \bool_if:NF \l__nicematrix_last_row_without_value_bool { \int_compare:nNnF \l__nicematrix_last_row_int = \c@iRow { __nicematrix_error:n { Wrong~last~row } \int_gset_eq:NN \l__nicematrix_last_row_int \c@iRow } } } \int_gset_eq:NN \c@jCol \g__nicematrix_col_total_int \bool_if:nTF \g__nicematrix_last_col_found_bool { \int_gdecr:N \c@jCol } { \int_compare:nNnT \l__nicematrix_last_col_int > { -1 } { __nicematrix_error:n { last~col~not~used } } } \int_gset_eq:NN \g__nicematrix_row_total_int \c@iRow \int_compare:nNnT \l__nicematrix_last_row_int > { -1 } { \int_gdecr:N \c@iRow } \int_compare:nNnT \l__nicematrix_first_col_int = 0 { \skip_horizontal:N \col@sep \skip_horizontal:N \g__nicematrix_width_first_col_dim } \bool_if:NTF \l__nicematrix_NiceArray_bool { \str_case:VnF \l__nicematrix_baseline_tl { b __nicematrix_use_arraybox_with_notes_b: c __nicematrix_use_arraybox_with_notes_c: } __nicematrix_use_arraybox_with_notes: } { \int_compare:nNnTF \l__nicematrix_first_row_int = 0 { \dim_set_eq:NN \l_tmpa_dim \g__nicematrix_dp_row_zero_dim \dim_add:Nn \l_tmpa_dim \g__nicematrix_ht_row_zero_dim } { \dim_zero:N \l_tmpa_dim } \int_compare:nNnTF \l__nicematrix_last_row_int > { -2 } { \dim_set_eq:NN \l_tmpb_dim \g__nicematrix_ht_last_row_dim \dim_add:Nn \l_tmpb_dim \g__nicematrix_dp_last_row_dim } { \dim_zero:N \l_tmpb_dim } \hbox_set:Nn \l_tmpa_box { \c_math_toggle_token \tl_if_empty:NF \l__nicematrix_delimiters_color_tl { \color { \l__nicematrix_delimiters_color_tl } } \exp_after:wN \left \g__nicematrix_left_delim_tl \vcenter { % \skip_vertical:N -\l_tmpa_dim \skip_vertical:n { -\l_tmpa_dim - \arrayrulewidth } \hbox { \bool_if:NTF \l__nicematrix_NiceTabular_bool { \skip_horizontal:N -\tabcolsep } { \skip_horizontal:N -\arraycolsep } __nicematrix_use_arraybox_with_notes_c: \bool_if:NTF \l__nicematrix_NiceTabular_bool { \skip_horizontal:N -\tabcolsep } { \skip_horizontal:N -\arraycolsep } } % \skip_vertical:N -\l_tmpb_dim \skip_vertical:n { -\l_tmpb_dim + \arrayrulewidth } } \tl_if_empty:NF \l__nicematrix_delimiters_color_tl { \color { \l__nicematrix_delimiters_color_tl } } \exp_after:wN \right \g__nicematrix_right_delim_tl \c_math_toggle_token } \bool_if:NTF \l__nicematrix_delimiters_max_width_bool { __nicematrix_put_box_in_flow_bis:nn \g__nicematrix_left_delim_tl \g__nicematrix_right_delim_tl } __nicematrix_put_box_in_flow: } \bool_if:NT \g__nicematrix_last_col_found_bool { \skip_horizontal:N \g__nicematrix_width_last_col_dim \skip_horizontal:N \col@sep } \bool_if:NF \l__nicematrix_Matrix_bool { \int_compare:nNnT \c@jCol < \g__nicematrix_static_num_of_col_int { __nicematrix_error:n { columns~not~used } } } \group_begin: \globaldefs = 1 __nicematrix_msg_redirect_name:nn { columns~not~used } { error } \group_end: __nicematrix_after_array: \egroup \iow_now:Nn @mainaux { \ExplSyntaxOn } \iow_now:Nn @mainaux { \char_set_catcode_space:n { 32 } } \iow_now:Nx @mainaux { \tl_gset:cn { c__nicematrix_ \int_use:N \g__nicematrix_env_int _ tl } { \exp_not:V \g__nicematrix_aux_tl } } \iow_now:Nn @mainaux { \ExplSyntaxOff } \bool_if:NT \c__nicematrix_footnote_bool \endsavenotes } \ExplSyntaxOff \makeatother

\begin{document}

\begin{equation} \rlap{\rule{57mm}{0.11pt}}0 = \begin{NiceMatrix}[baseline=2] 0 & 1 \ \rlap{\color{red}\rule{5.7mm}{0.11pt}}0 & 1 \ 0 & 1 \ \end{NiceMatrix}\quad 0 = \begin{pNiceMatrix}[baseline=2] 0 & 2 \ \rlap{\color{red}\rule{5.7mm}{0.11pt}}0 & 2 \ 0 & 2 \ \end{pNiceMatrix}\quad 0 = \begin{bNiceMatrix}[baseline=2] 0 & 3 \ \rlap{\color{red}\rule{5.7mm}{0.11pt}}0 & 3 \ 0 & 3 \ \end{bNiceMatrix} \end{equation}

\end{document}

The code inside \AtBeginDocument{...} is extracted from my own patch file (https://github.com/RuixiZhang42/font-pairing-guide/blob/master/mtpro2-patch.tex), lines 852–866 as of this writing. It covers only array.sty. A more thorough fix is provided as the code from line 850 to line 911.

Ruixi Zhang
  • 9,553
  • That's very interesting. However, I think that there is also a bug in nicematrix related in the key baseline. – F. Pantigny Nov 21 '21 at 13:46
  • 1
    @F.Pantigny Found it! This is indeed a dual bug (one in the kernel and one in the package). Misalignment happens when using baseline key and using delimited matrix. After fixing the kernel bug, the first OP example is correct, but the second and the third remain unfixed. The offset seems exactly \arrayrulewidth, so the problem lies within \endNiceArrayWithDelims. – Ruixi Zhang Nov 21 '21 at 17:02
  • +1: Impressive answer and thanks for your efforts – Dr. Manuel Kuehner Nov 21 '21 at 17:40
  • 2
    I will write a new version of nicematrix to correct that bug. – F. Pantigny Nov 22 '21 at 13:24
  • Hi @RuixiZhang, your patch works very well :) But... it seems that the \Block{}{new\\line} has also been affected (you can see the updates above). Do you have any idea? Thanks. – yzhang Nov 27 '21 at 11:28
4

The version 6.4 of nicematrix (2021/11/23) solves that bug of nicematrix.

Here is a MWE.

Eventually, I don't use the Ruixi Zhang's patch of {array} of LaTeX (see other answer) because it seems that it has consequences when the command \Block is used with \\ somewhere in the matrix.

\documentclass{article}
\usepackage{nicematrix}
\begin{document}

\begin{equation} \rlap{\rule{57mm}{0.11pt}}0 = \begin{NiceMatrix}[baseline=2] 0 & 1 \ 0 & 1 \ 0 & 1 \ \end{NiceMatrix}\quad 0 = \begin{pNiceMatrix}[baseline=2] 0 & 2 \ 0 & 2 \ 0 & 2 \ \end{pNiceMatrix}\quad 0 = \begin{bNiceMatrix}[baseline=2] 0 & 3 \ 0 & 3 \ 0 & 3 \ \end{bNiceMatrix} \end{equation}

\end{document}

As usual with nicematrix, you need several compilations.

Output of the above code

F. Pantigny
  • 40,250
  • Thanks! But now the \Block{}{new\\line} is broken... (with that patch). Do you have any insight on this? – yzhang Nov 26 '21 at 11:22
  • @user8682688: That's strange. You should ask another question or send me directly a mail (you will find my address on the documentation of nicematrix). – F. Pantigny Nov 26 '21 at 12:13
  • It is not introduced by the new version (although the problem still exists). I have updated it in the question area above. (I meant the patch by Ruixi Zhang, sorry for the confusion!) – yzhang Nov 26 '21 at 12:57
2

This is not a real answer, but only a possible \patchcmd way to patch the (end of) LaTeX3 environment NiceArrayWithDelims, in order to shorten the corresponding part of the "complete code" example from @RuixiZhang's answer.

\makeatletter
\ExplSyntaxOn
% patch \cs{environment NiceArrayWidthDelims end aux }
\char_set_catcode_space:n {32} % restore catcode of space char

\expandafter\patchcmd \csname environment~ NiceArrayWithDelims~ end~ aux~ \endcsname {\skip_vertical:N -\l_tmpa_dim} {\skip_vertical:n {-\l_tmpa_dim -\arrayrulewidth }} {}{\fail}

\expandafter\patchcmd \csname environment~ NiceArrayWithDelims~ end~ aux~ \endcsname {\skip_vertical:N -\l_tmpb_dim } {\skip_vertical:n {-\l_tmpb_dim +\arrayrulewidth }} {}{\fail} \ExplSyntaxOff \makeatother

muzimuzhi Z
  • 26,474
  • I tried the \expandafter\patchcmd \csname environment~ NiceArrayWithDelims~ end~ aux~ \endcsname trick but it didn't work: I was missing the \char_set_catcode_space:n {32} part... – Ruixi Zhang Nov 21 '21 at 22:58
  • @RuixiZhang It's xpatch's \tracingpatches that helps me find the catcode issue of space. – muzimuzhi Z Nov 21 '21 at 23:03
  • Very nice detective work! Although, at the end, \csname environment~ NiceArrayWithDelims~ end~ aux~ \endcsname is xparse's hidden/private/internal definition, and is subject to change without notice. Sigh... not an elegant fix no matter how hard we tried... :( – Ruixi Zhang Nov 21 '21 at 23:11
  • I remember there was a feature request asking for commands to patch latex3 (or xparse) cmds & envs, but can't find it after some search queries (in both latex2e and latex3's repo). – muzimuzhi Z Nov 21 '21 at 23:20
  • I will write a new version of nicematrix to correct that bug. – F. Pantigny Nov 22 '21 at 13:25