% ---------------------------------------------------------------------------
% aTableau - Andrew Mathas (C) 2022-2025
% Modified: 08:08 Friday, 10 October 2025
% ---------------------------------------------------------------------------
%
% A LaTeX package for symmetric group combinatorics:
%   - abacuses
%   - multitableau
%   - ribbon tableau
%   - shifted tableau
%   - skew tableau
%   - tableaux
%   - tabloids
%   - Young diagrams
%
% Released under the LaTeX Project Public License v1.3c or later
% See http://www.latex-project.org/lppl.txt
%
% The package is author-maintained in the sense of this license.  The
% author can be contacted via email at andrew.mathas@gmail.com.
% For bugs and other issues with the package please use
% the package repository github.com/AndrewMathas/aTableau/

% ---------------------------------------------------------------------------
% aTableau release date and version number
\def\aTableau@version{2.2.0}
\def\aTableau@release{2025-10-10}

% ---------------------------------------------------------------------------
% load TikZ early to avoid \ExplSyntaxOn...\ExplSyntaxOff wrappers
\RequirePackage{tikz}
\usetikzlibrary{
    arrows.meta,      % for arrows in abacus ends
    matrix,           % for delimiters in multitableaux
    shapes.geometric, % for diamonds in Ukrainian/Australian tableaux
    shapes.misc,      % for 'rounded rectangle' in traditional abacuses
}

% ---------------------------------------------------------------------------
\providecommand \IfFormatAtLeastTF { \@ifl@t@r \fmtversion }

% Correct for negative signs in contents being too long to fit in a tableau
\ifcsname shortminus\endcsname
  % define the \shortminus command only if it does not already exist
\else
  \RequirePackage{amsfonts}
  \DeclareMathSymbol{\shortminus}{\mathbin}{AMSa}{"39}
\fi

% ---------------------------------------------------------------------------
\providecommand\DeclareRelease[3]{}
\providecommand\DeclareCurrentRelease[2]{}
\DeclareRelease{\aTableau@version}{\atableau@release}{atableau.sty}
\DeclareCurrentRelease{}{\aTableau@release}

\ProvidesExplPackage{atableau}
  {\aTableau@release}
  {\aTableau@version}
  {for symmetric group combinatorics}

% ---------------------------------------------------------------------------
% Give a warning if the LaTeX installation is older than TeXLive 2024
% It is not clear to me where the actual cut-off is...
\NeedsTeXFormat{LaTeX2e}% [2023-01-01]
\IfFormatAtLeastTF {2024-04-13} {}
{
    \PackageWarning {aTableau}
    {
      The~aTableau~package~uses~many~features~from~LaTeX3.~Your~LaTeX~installation~is~not~very~recent.~
      If~the~package~gives~you~errors,~then~you~may~need~to~update~your~LaTeX~installation~to~at~least~TeXLive~2024.
    }
}

%  \__aTableau_debug:n { message }  -- for debugging in development mode
%  \__aTableau_debug:n ** must occupy ONLY ONE LINE so that it can be removed by ctanify script**
\cs_new_protected:Npn \__aTableau_debug:n #1 {\typeout{ #1 }}

% ---------------------------------------------------------------------------
% aTableau error messages

\msg_new:nnnn { aTableau } { empty-tableau-row }
  { Row~#1~of~tableau~is~empty }
  { Empty~tableau~rows~are~not~supported}

\msg_new:nnnn { aTableau } { missing-runner-labels }
  { Your~abacus~has~a~different~number~of~runners~and~runner~labels }
  { The~number~of~labels~given~to~the~'runner~labels'~must~match~the~number~of~abacus~runners }

\msg_new:nnnn { aTableau } { missing-style }
  { The~'#1'~styles~key~is~missing~a~value }
  { The~styles~key~accepts~a~comma~separated~list~of~key-value~pairs~for~defining~TikZ~styles }

\msg_new:nnnn { aTableau } { invalid-dots}
  { Invalid~specifications~for~dotted~rows~or~dotted~cols }
  { You~can~only~use~these~options~for~interior~rows~and~columns~in~the~diagrams}

\msg_new:nnnn { aTableau } { invalid-ribbon-head }
  { Invalid~ribbon~head:~the~row~and~column~indices~of~the~ribbon~head~must~be~given:~'#1'}
  { Ribbon~specifications~must~be~for~the~form:~(optional~ribbon~style)[style]<row><col>+sequences~of~r's~and~c's~with~style}

\msg_new:nnnn { aTableau } { invalid-ribbon-path }
  { Ribbon~specifications~should~not~back-track:~rR,~Rr,~cC~and~Cc~are~not~allowed!}
  { The~ribbon~specifications~do~not~allow~paths~to~immediately~backtrack~by~using~rR,~Rr,~cC~or~Cc}

\msg_new:nnnn { aTableau } { invalid-ribbon-specification }
  { Invalid~ribbon~specification~'#1':~expecting~r,~c,~R~or~C. }
  { Ribbon~specifications~must~be~of~the~form:~[optional~style]<row><col>~sequences~of~r's,~c's,~R's~and~C's~with~style}

\msg_new:nnnn { aTableau } { inside-tikzpicture }
  { aTableau~command~without~(x,y)-coordinates~CANNOT~be~inside~a~tikzpicture~environment! \@currenvir}
  { You~need~to~be~inside~a~tikzpicture~environment~whenever~you~use~an~aTableau~command~with~(x,y)-coordinates }

\msg_new:nnnn { aTableau } { invalid-key }
  { Invalid~key~'#1'~in~aTableau~settings. }
  { A~list~of~supported~keys~can~be~found~in~the~aTableau~manual. }

\msg_new:nnnn { aTableau } { outside-tikzpicture }
  { aTableau~command~with~(x,y)-coordinates~NOT~inside~a~tikzpicture~environment! }
  { You~need~to~be~inside~a~tikzpicture~environment~whenever~you~use~an~aTableau~command~with~(x,y)-coordinates }

\msg_new:nnnn { aTableau } { many-beads }
  { You~have~set~'beads=\int_use:N\l__aTableau_beads_int',~yet~your~abacus~requires~'#1'~beads }
  { This~is~just~a~warning~message~because~your~abacus~has~more~beads~than~expected.}

\msg_new:nnnn { aTableau } { many-rows }
  { You~have~set~'rows=\int_use:N\l__aTableau_rows_int',~yet~your~abacus~requires~'#1'~rows }
  { This~is~just~a~warning~message~because~your~abacus~has~more~rows~than~expected.}

\msg_new:nnnn { aTableau } { quotient-components }
  { The~quotient~has~too~many~components:~#1 }
  { When~specifying~a~partition~by~its~quotient~in~an~abacus,~the~number~of~components~in~the~quotients~can~be~at~most~the~number~of~runners~in~the~abacus }

\msg_new:nnnn { aTableau } { unrecognised-abacus-label }
  { Unrecognised~abacus~label~'#1'. }
  { The~possible~abacus~labels~are~beads,~residues,~rows~and~shape}

\msg_new:nnnn { aTableau } { unrecognised-aTableau-command }
  { Unrecognised~aTableau~command~'#1' }
  { The~first parameter for \textbackslash NewaTableauCommand~must~be~the~name~of~one~of~the~aTableau~picture~commands }

\msg_new:nnnn { aTableau } { unrecognised-entries }
  { Unrecognised~entries~value:~'#1' }
  { The~possible~diagram~values~for~entries~are:~contents,~first,~hooks,~last,~and~residues}

\msg_new:nnnn { aTableau } { unknown-abacus-end }
  { Unrecognised~abacus~ends~setting:~'#1' }
  { The~supported~ends~for~the~top/bottom~of~the~abacus~are:~-,~\c_underscore_str~,.~,|,~and~>}

\msg_new:nnnn { aTableau } { unknown-beamer }
  { Unsupported~beamer~command~'#1' }
  { The~supported~beamer~commands~are~invisible,~only,~onslide,~uncover~and~visible,~all~of~which~should~be~entered~WITHOUT~a~backslash }

\msg_new:nnnn { aTableau } { unknown-cartan }
  { Unrecognised~Cartan~type~'#1' }
  { The~supported~Cartan~types~are~(affine)~types~A,~AA,~C~and~DD}

\msg_new:nnnn { aTableau } { unknown-baseline }
  { Unrecognised~baseline~option:~'#1' }
  { The~supported~halign~options~are~bottom,~centre,~left,~and~top }

\msg_new:nnnn { aTableau } { unknown-halign }
  { Unrecognised~halign~option:~'#1' }
  { The~supported~halign~options~are~centre,~left,~and~right }

\msg_new:nnnn { aTableau } { unknown-theme }
  { Unrecognised~colour~theme~option:~'#1' }
  { The~supported~colour~themes~are~default,~classic,~and~natural }

\msg_new:nnnn { aTableau } { unknown-valign }
  { Unrecognised~valign~option:~#1 }
  { The~supported~valign~options~are~bottom,~centre~and~top }

\msg_new:nnnn { aTableau } { unsupported-abacus-end }
  { Traditional~abacuses~are~not~compatible~with~'#1'~abacus~ends }
  { Traditional~abacuses~only~support~the~-,~_~and~|~abacus~end~styles~}

% ---------------------------------------------------------------------------
% aTableau variables
% The default values are controlled by the aTableau keys

\bool_new:N \l__aTableau_beta_numbers_bool% true if specifying beta numbers
\bool_new:N \l__aTableau_border_bool      % true if drawing tableau border
\bool_new:N \l__aTableau_boxes_bool       % true if drawing inner tableau walls
\bool_new:N \l__aTableau_conjugate_bool   % true if drawing conjugate tableau/diagram
\bool_new:N \l__aTableau_cover_border_bool% true if drawing cover border
\bool_new:N \l__aTableau_cover_boxes_bool % true if drawing cover boxes
\bool_new:N \l__aTableau_framed_bool      % true if drawing framed abacus runners
\bool_new:N \l__aTableau_separators_bool  % true if drawing separators for multishapes
\bool_new:N \l__aTableau_shifted_bool     % true if a shifted tableau
\bool_new:N \l__aTableau_skew_border_bool % true if drawing skew border
\bool_new:N \l__aTableau_skew_boxes_bool  % true if drawing skew boxes
\bool_new:N \l__aTableau_tabloid_bool     % true if a tabloid
\bool_new:N \l__aTableau_traditional_bool % true if a traditional abacus

\clist_new:N \l__aTableau_adjacent_edges_clist  % list of possible adjacent edges in a ribbon

\fp_new:N \l__aTableau_abacus_bottom_fp   % row index of the bottom of the abacus
\fp_new:N \l__aTableau_abacus_col_dx_fp   % change in x-coordinate between columns in an abacus
\fp_new:N \l__aTableau_abacus_col_dy_fp   % change in y-coordinate between columns in an abacus
\fp_new:N \l__aTableau_abacus_ht_fp       % height of the tableaux nodes/separation between abacus beads
\fp_new:N \l__aTableau_abacus_row_dx_fp   % change in x-coordinate between rows in an abacus
\fp_new:N \l__aTableau_abacus_row_dy_fp   % change in y-coordinate between rows in an abacus
\fp_new:N \l__aTableau_abacus_top_fp      % row index of the top of the abacus
\fp_new:N \l__aTableau_abacus_wd_fp       % width of the tableaux nodes/separation between abacus runners
\fp_new:N \l__aTableau_bead_height_fp     % abacus bead height
\fp_new:N \l__aTableau_bead_width_fp      % abacus bead width
\fp_new:N \l__aTableau_beam_height_fp     % width of abacus rods/runners when using traditional style
\fp_new:N \l__aTableau_box_col_dx_fp      % change in x-coordinate between columns in a tableau
\fp_new:N \l__aTableau_box_col_dy_fp      % change in y-coordinate between columns in a tableau
\fp_new:N \l__aTableau_box_ht_fp          % height of a tableau box
\fp_new:N \l__aTableau_box_row_dx_fp      % change in x-coordinate between rows in a tableau
\fp_new:N \l__aTableau_box_row_dy_fp      % change in y-coordinate between rows in a tableau
\fp_new:N \l__aTableau_box_wd_fp          % width of a tableau box
\fp_new:N \l__aTableau_rotate_fp          % rotation angle, set by rotate key
\fp_new:N \l__aTableau_rows_fp            % number of rows in abacus/tableau
\fp_new:N \l__aTableau_script_fp          % scaling when used as a subscript
\fp_new:N \l__aTableau_scriptscript_fp    % scaling when used as a subsubscript
\fp_new:N \l__aTableau_separation_fp      % distance between multitableau
\fp_new:N \l__aTableau_tick_length_fp     % half the length of the abacus ticks
\fp_new:N \l__aTableau_x_fp               % x-coordinate of the origin of the diagram
\fp_new:N \l__aTableau_xa_fp              % scratch x-coordinate of a node/bead
\fp_new:N \l__aTableau_xb_fp              % scratch x-coordinate of a node/bead
\fp_new:N \l__aTableau_xl_fp              % x-coordinate of a tableau node/bead
\fp_new:N \l__aTableau_xmax_fp            % maximum x-coordinate for multidiagrams and multitableaux
\fp_new:N \l__aTableau_xscale_fp          % scaling in the x-direction
\fp_new:N \l__aTableau_xsep_fp            % x-coordinate difference to next separator for multishapes
\fp_new:N \l__aTableau_y_fp               % y-coordinate of the origin of the diagram
\fp_new:N \l__aTableau_ya_fp              % scratch y-coordinate of a node/bead
\fp_new:N \l__aTableau_yb_fp              % scratch y-coordinate of a node/bead
\fp_new:N \l__aTableau_yl_fp              % y-coordinate of a tableau node/bead
\fp_new:N \l__aTableau_ymax_fp            % maximum y-coordinate for multidiagrams and multitableaux
\fp_new:N \l__aTableau_ymin_fp            % minimum y-coordinate for multidiagrams and multitableaux
\fp_new:N \l__aTableau_yscale_fp          % scaling in the x-direction

\int_new:N \l__aTableau_beads_int         % number of beads on the abacus
\int_new:N \l__aTableau_charge_int        % charge for current component
\int_new:N \l__aTableau_col_int           % current column index
\int_new:N \l__aTableau_c_int             % scratch column counter
\int_new:N \l__aTableau_cols_int          % number of columns in a multitableau/abacus
\int_new:N \l__aTableau_component_int     % component in multidiagrams and tableaux
\int_new:N \l__aTableau_e_int             % quantum characteristic
\int_new:N \l__aTableau_row_int           % current row index
\int_new:N \l__aTableau_r_int             % scratch row counter
\int_new:N \l__aTableau_rows_int          % number of rows in abacus

\prop_new:N \l__aTableau_colour_theme_prop% colour theme specifications
\prop_new:N \l__aTableau_ribbon_prop      % border paths in a ribbon (no speed difference using \prop_new_linked:N)

\seq_new:N \l__aTableau_charge_seq        % sequences of charges = residue/content offset
\seq_new:N \l__aTableau_colours_seq       % tableau fill colours
\seq_new:N \l__aTableau_component_seq     % multipartition
\seq_new:N \l__aTableau_conjugate_seq     % conjugate partition
\seq_new:N \l__aTableau_dotted_cols_seq   % columns with dots
\seq_new:N \l__aTableau_dotted_rows_seq   % rows with dots
\seq_new:N \l__aTableau_cover_seq         % the inner partition for the cover shape
\seq_new:N \l__aTableau_multidotted_cols_seq % sequence of dotted columns for multi shapes
\seq_new:N \l__aTableau_multidotted_rows_seq % sequence of dotted rows for multi shapes
\seq_new:N \l__aTableau_multicover_seq    % a sequence of cover partitions for multi shapes
\seq_new:N \l__aTableau_multilabel_seq    % a sequence of labels for multi shapes
\seq_new:N \l__aTableau_multipaths_seq    % a sequence of ribbon paths for multi shapes
\seq_new:N \l__aTableau_multiribbons_seq  % a sequence of ribbon for multi shapes
\seq_new:N \l__aTableau_multiskew_seq     % a sequence of skew partitions for multi shapes
\seq_new:N \l__aTableau_multisnobs_seq    % a sequence of snobs for multi shapes
\seq_new:N \l__aTableau_rcs_seq           % ribbon row, column indices and dummy sequence for abacuses
\seq_new:N \l__aTableau_row_labels_seq    % labels for the abacus rows
\seq_new:N \l__aTableau_runner_labels_seq % labels for the abacus runners
\seq_new:N \l__aTableau_shape_seq         % a partition
\seq_new:N \l__aTableau_skew_seq          % the inner partition for a skew shape
\seq_new:N \l__aTableau_styles_seq        % ribbon/bead styles
\seq_new:N \l__aTableau_texts_seq         % ribbon/bead texts
\seq_new:N \l__aTableau_xoffsets_seq      % x-offsets for the (1,1)-nodes in a multi-tableau
\seq_new:N \l__aTableau_xsep_seq          % x-coordinates separators for multishapes
\seq_new:N \l__aTableau_yoffsets_seq      % t-offsets for the (1,1)-nodes in a multi-tableau

\tl_new:N \l__aTableau_abacus_bottom_tl   % specifies the bottom of the abacus
\tl_new:N \l__aTableau_abacus_top_tl      % specifies the top of the abacus
\tl_new:N \l__aTableau_bead_font_tl       % font for abacus beads
\tl_new:N \l__aTableau_bead_shape_tl      % shape of abacus beads
\tl_new:N \l__aTableau_bead_text_tl       % text colour of abacus beads
\tl_new:N \l__aTableau_bead_tl            % abacus head colour
\tl_new:N \l__aTableau_beamer_tl          % name of beamer overlay command
\tl_new:N \l__aTableau_border_tl          % diagram border
\tl_new:N \l__aTableau_box_fill_tl        % fill colour for tableau boxes
\tl_new:N \l__aTableau_box_font_tl        % font for tableau boxes
\tl_new:N \l__aTableau_box_shape_tl       % shape of tableau boxes
\tl_new:N \l__aTableau_box_text_tl        % text colour for tableau boxes
\tl_new:N \l__aTableau_capture_exp_tl     % contains captured exponent
\tl_new:N \l__aTableau_capture_part_tl    % contains captured part
\tl_new:N \l__aTableau_capture_style_tl   % contains captured style
\tl_new:N \l__aTableau_capture_txt_tl     % contains captured text
\tl_new:N \l__aTableau_cartan_tl          % the Cartan type
\tl_new:N \l__aTableau_cover_border_tl    % cover border colour
\tl_new:N \l__aTableau_empty_tl           % symbol for empty tableau/diagram in a multi tableau/diagram
\tl_new:N \l__aTableau_entries_tl         % custom entries in tableau
\tl_new:N \l__aTableau_inner_tl           % colour of tableau inner walls
\tl_new:N \l__aTableau_label_tl           % a label to print on an tableau/diagram
\tl_new:N \l__aTableau_left_delimiter_tl  % the left delimiter for multitableau and multidiagrams
\tl_new:N \l__aTableau_multiprefix_tl     % intermediate prefix used when constructing multinode names
\tl_new:N \l__aTableau_name_tl            % name of a tableau node
\tl_new:N \l__aTableau_outer_tl           % colour of tableau outer walls
\tl_new:N \l__aTableau_path_box_tl        % node for ribbon paths
\tl_new:N \l__aTableau_paths_tl           % ribbon paths to add to tableau/diagram
\tl_new:N \l__aTableau_prefix_tl          % prefix for node names
\tl_new:N \l__aTableau_ribbon_box_tl      % box entry for ribbons
\tl_new:N \l__aTableau_ribbon_a_tl        % a corner on a cell being added to a ribbon
\tl_new:N \l__aTableau_ribbon_b_tl        % a corner on a cell being added to a ribbon
\tl_new:N \l__aTableau_ribbon_c_tl        % a corner on a cell being added to a ribbon
\tl_new:N \l__aTableau_ribbon_d_tl        % a corner on a cell being added to a ribbon
\tl_new:N \l__aTableau_ribbon_path_tl     % a ribbon in the ribbon tableau
\tl_new:N \l__aTableau_ribbon_style_tl    % optional style used for current path< ribbon or snob
\tl_new:N \l__aTableau_ribbon_type_tl     % the type of ribbon, which is either path, ribbon or snob
\tl_new:N \l__aTableau_ribbons_tl         % ribbons to add to tableau/diagram
\tl_new:N \l__aTableau_right_delimiter_tl % the right delimiter for multitableau and multidiagrams
\tl_new:N \l__aTableau_runner_tl          % abacus runner colour
\tl_new:N \l__aTableau_separator_fg_tl    % foreground colour of the separator
\tl_new:N \l__aTableau_separator_tl       % the separator between multitableau: | or , or ...
\tl_new:N \l__aTableau_shading_tl         % the type of shading to use for the abacus beads
\tl_new:N \l__aTableau_show_tl            % automatic tableau/bead labelling
\tl_new:N \l__aTableau_skew_border_tl     % skew border colour
\tl_new:N \l__aTableau_snob_box_tl        % box entry for snobs
\tl_new:N \l__aTableau_snobs_tl           % snob ribbons to add to tableau/diagram
\tl_new:N \l__aTableau_starstyle_tl       % current star style in use
\tl_new:N \l__aTableau_styled_nodes_tl    % token lists of nodes with non-default style
\tl_new:N \l__aTableau_tick_tl            % abacus tick colour
\tl_new:N \l__aTableau_tikz_after_tl      % TikZ commands for after the diagram
\tl_new:N \l__aTableau_tikz_before_tl     % TikZ commands for before the diagram
\tl_new:N \l__aTableau_tikzpicture_tl     % TikZ environment settings
\tl_new:N \l__aTableau_unstyled_nodes_tl  % token lists of nodes with efault style

% ---------------------------------------------------------------------------
% aTableau colour themes

% We put the colour definitions into a property list so that we can easily add
% them in the manual without manually copying them across

\prop_set_from_keyval:Nn \l__aTableau_colour_theme_prop
{
  % default theme
  default_Main  = 00008B,
  default_Inner = 0073E6,
  default_Star  = E6F7FF,
  default_Skew  = 818589,
  default_Fill  = F8F8F8,
  default_Rod   = B3B3c2,

  % classic theme
  classic_Main  = 2C2C2C,
  classic_Inner = 555555,
  classic_Star  = D6D6D6,
  classic_Skew  = 777777,
  classic_Fill  = EAEAEA,
  classic_Rod   = A0A0A0,

  % natural theme
  natural_Main  = 5C3D2E,
  natural_Inner = E6C29F,
  natural_Star  = FFF2E5,
  natural_Skew  = A48C74,
  natural_Fill  = FFF8F2,
  natural_Rod   = C4A484, % AD6434
}

% usage: \__aTableau_define_html_colour:nn {theme name} {html colour}
% define the HTML colour aTableau#1 = #2
\cs_new_protected:Npn \__aTableau_define_html_colour:nn #1 #2
{
    \definecolor {aTableau#1} {HTML} {#2}
}

% ---------------------------------------------------------------------------
% set the box fill colour when colours is used

\cs_new_protected:Npn \__aTableau_set_colours:n #1
{
    \seq_set_split:Nnn \l__aTableau_colours_seq {,} {#1}
    \cs_set_eq:NN \l__aTableau_set_box_fill:nn \__aTableau_set_box_fill_colour:nn
    \int_set:No \l__aTableau_e_int { \seq_count:N \l__aTableau_colours_seq }
    \tl_if_eq:NnT \l__aTableau_cartan_tl {C}
    {
        \int_set:No \l__aTableau_e_int { \l__aTableau_e_int-1 }
    }
}

\cs_set_eq:NN \l__aTableau_set_box_fill:nn  \use_none:nn

% usage: \__aTableau_set_tableau_fill_colour:nn {row} {column}
% Set the fill colour in a tableau for the specified row and column
\cs_new_protected:Npn \__aTableau_set_box_fill_colour:nn #1 #2
{
    \int_set:No \l_tmpa_int { 1+\__aTableau_residue:nn { \int_eval:n{#2-#1}} {\l__aTableau_e_int} }
    \tl_set:Ne \l__aTableau_box_fill_tl {\seq_item:NV \l__aTableau_colours_seq \l_tmpa_int }
}

% ---------------------------------------------------------------------------
% TikZ styling of aTableau components

\tikzset{
  % -----------------------------------------------------------------------
  % Allow ball shading to be disabled by the 'unshaded' styl
  % The essential idea comes from https://tex.stackexchange.com/a/85750/234252
  unshaded/.code={ \tikz@addmode{\tikz@mode@shadefalse } },
  %
  % a shorthand, bead=<colour>, for setting the ball colour
  bead/.style={ball~color=#1},
  %
  % -----------------------------------------------------------------------
  % aTableau settings are in the aTableau family
  aTableau/.is~family,
  aTableau/.cd,
    % -----------------------------------------------------------------------
    % Styles for tableaux and diagrams
    % -----------------------------------------------------------------------
    % inner tableau wall
    innerWall/.style = {
        line~cap = rect,
        thin,
    },
    % outer tableau wall
    border/.style = {
        % shifting allows us to use \__aTableau_set_box_coordinates:nnn when drawing the border
        shift = {(\fp_eval:n{-(\l__aTableau_box_row_dx_fp+\l__aTableau_box_col_dx_fp)/2},
                  \fp_eval:n{-(\l__aTableau_box_row_dy_fp+\l__aTableau_box_col_dy_fp)/2})},
        line~cap = rect,
        very~thick,
        draw = \l__aTableau_outer_tl,
    },
    % cover walls
    coverBorder/.style = {
        % shifting allows us to use \__aTableau_set_box_coordinates:nnn when drawing the border
        shift = {(\fp_eval:n{-(\l__aTableau_box_row_dx_fp+\l__aTableau_box_col_dx_fp)/2},
                  \fp_eval:n{-(\l__aTableau_box_row_dy_fp+\l__aTableau_box_col_dy_fp)/2})},
        draw  = \l__aTableau_cover_border_tl,
        thick
    },
    % skew walls
    skewBorder/.style = {
        % shifting allows us to use \__aTableau_set_box_coordinates:nnn when drawing the border
        shift = {(\fp_eval:n{-(\l__aTableau_box_row_dx_fp+\l__aTableau_box_col_dx_fp)/2},
                  \fp_eval:n{-(\l__aTableau_box_row_dy_fp+\l__aTableau_box_col_dy_fp)/2})},
        draw  = \l__aTableau_skew_border_tl,
        thick
    },
    % -----------------------------------------------------------------------
    % styles for tableau boxes in Young diagrams
    aTableauBox/.style = {
        anchor         = center,
        inner~sep      = 0pt,
        rotate         = \fp_use:N \l__aTableau_rotate_fp,
        minimum~height = \fp_use:N \l__aTableau_box_ht_fp cm,
        minimum~width  = \fp_use:N \l__aTableau_box_wd_fp cm,
        shape          = \l__aTableau_box_shape_tl,
        font           = \l__aTableau_box_font_tl,
        text           = \l__aTableau_box_text_tl,
    },
    tableauBox/.style = {
        aTableau/aTableauBox,
        aTableau/innerWall,
        draw = \l__aTableau_inner_tl,
        fill = \l__aTableau_box_fill_tl,
    },
    % default cover box style
    coverBox/.style = {
        aTableau/aTableauBox,
        aTableau/innerWall,
        draw  = \l__aTableau_cover_border_tl,
        fill  = aTableauFill,
    },
    % default skew box style
    skewBox/.style = {
        aTableau/aTableauBox,
        aTableau/innerWall,
        draw  = \l__aTableau_skew_border_tl,
        fill  = aTableauFill,
    },
    % -----------------------------------------------------------------------
    % box styles for paths, ribbons and snobs
    pathBox/.style = {
        aTableau/aTableauBox,
        draw = none, % border disabled by default
    },
    ribbonBox/.style = {
        aTableau/aTableauBox,
    },
    snobBox/.style = {
        aTableau/aTableauBox,
    },
    % -----------------------------------------------------------------------
    % default path style
    path/.style = {
        draw = \l__aTableau_inner_tl,
    },
    % default ribbon style
    ribbon/.style = {
        aTableau/innerWall,
        % shifting allows us to use \__aTableau_set_box_coordinates:nnn when drawing the wall
        shift = {(\fp_eval:n{(\l__aTableau_box_row_dx_fp+\l__aTableau_box_col_dx_fp)/2},
                  \fp_eval:n{(\l__aTableau_box_row_dy_fp+\l__aTableau_box_col_dy_fp)/2})},
        draw = \l__aTableau_inner_tl,
    },
    % default snob style = ribbon style
    snob/.style = {
        aTableau/innerWall,
        % shifting allows us to use \__aTableau_set_box_coordinates:nnn when drawing the wall
        shift = {(\fp_eval:n{(\l__aTableau_box_row_dx_fp+\l__aTableau_box_col_dx_fp)/2},
                  \fp_eval:n{(\l__aTableau_box_row_dy_fp+\l__aTableau_box_col_dy_fp)/2})},
        draw = \l__aTableau_inner_tl,
    },
    % -----------------------------------------------------------------------
    % label styles
    label/.style = {
        shift = {(\fp_eval:n{-0.2*(\l__aTableau_box_row_dx_fp+\l__aTableau_box_col_dx_fp)},
                  \fp_eval:n{-0.2*(\l__aTableau_box_row_dy_fp+\l__aTableau_box_col_dy_fp)})},
        font=\scriptsize,
        text = \l__aTableau_inner_tl,
    },
    % -----------------------------------------------------------------------
    % tableau star style
    tableauStar/.style = {
        fill = aTableauStar,
        text = \l__aTableau_box_text_tl,
    },
    % -----------------------------------------------------------------------
    % cleared boxes for dotted rows and columns
    clearBoxes/.style = {
        draw = white,
        fill = white,
    },
    % -----------------------------------------------------------------------
    % dots used for dotted rows and columns
    dottedLine/.style = {
        densely~dotted,
        thick,
        draw = \l__aTableau_outer_tl,
    },
    % -----------------------------------------------------------------------
    % separators and delimiters
    separatorSymbol/.style = {
        text = \l__aTableau_separator_fg_tl,
    },
    % default separator line style
    separatorLine/.style = {
        thick,
        draw = \l__aTableau_separator_fg_tl,
    },
    % Delimiters around multitableaux and multidiagrams. To change the colour
    % of the delimiters we need to use \path[aTableau/delimiterPath] (x,y) node...
    delimiter/.style = {
        align = center,
        inner~sep = 0pt,
        minimum~height = \fp_use:N\l__aTableau_ymax_fp cm,
    },
    % hack to set the colour
    delimiterPath/.style = {
        every~delimiter/.style = { \l__aTableau_separator_fg_tl, },
    },
    leftDelimiter/.style = {
        aTableau/delimiter,
        left~delimiter = \l__aTableau_left_delimiter_tl,
        xshift = 2pt,
    },
    rightDelimiter/.style = {
        aTableau/delimiter,
        right~delimiter = \l__aTableau_right_delimiter_tl,
        xshift = -2pt,
    },
    % -----------------------------------------------------------------------
    % abacus beads and runners
    % -----------------------------------------------------------------------
    % style for the top and bottom of the abacus
    abacusEnds/.style = {
        aTableau/abacusRunner,
        >=stealth,
    },
    % abacus star style
    abacusStar/.style = {
        text = aTableauMain,
        ball~color = aTableauStar,
    },
    abacusRunner/.style = {
        draw = \l__aTableau_runner_tl,
        inner~color = \l__aTableau_runner_tl!20,
        outer~color = \l__aTableau_runner_tl!90,
        line~cap = rect,
        very~thick,
    },
    abacusTick/.style = {
        draw = \l__aTableau_tick_tl,
        semithick,
    },
    Label/.style = {
        anchor = center,
        draw = none,
        font = \scriptsize,
        inner~sep = 3pt,
        minimum~height = 1ex,
        minimum~width  = 1ex,
        text = aTableauInner,
    },
    rowLabel/.style = {
        aTableau/Label,
    },
    runnerLabel/.style = {
        aTableau/Label,
    },
    % -----------------------------------------------------------------------
    % the named coordinate for an abacus tick
    namedTick/.style = {
        minimum~height = \fp_to_decimal:N \l__aTableau_bead_height_fp cm,
        minimum~width  = \fp_to_decimal:N \l__aTableau_bead_width_fp cm,
        draw=none,
    },
    % -----------------------------------------------------------------------
    % styles for abacus beads
    abacusBead/.style = {
        ball~color     = \l__aTableau_bead_tl,
        font           = \l__aTableau_bead_font_tl,
        minimum~height = \fp_to_decimal:N \l__aTableau_bead_height_fp cm,
        minimum~width  = \fp_to_decimal:N \l__aTableau_bead_width_fp cm,
        shading        = \l__aTableau_shading_tl,
        shape          = \l__aTableau_bead_shape_tl,
        text           = \l__aTableau_bead_text_tl,
        anchor         = center,
        inner~sep      = 0pt,
    },
}

% ---------------------------------------------------------------------------
% set scales

% usage: \__aTableau_set_xscale:n {x-scale} : rescale the x-dimension
\cs_new_protected:Npn \__aTableau_set_xscale:n #1
{
    \fp_set:Nn \l__aTableau_abacus_wd_fp   { #1*\l__aTableau_abacus_wd_fp   }
    \fp_set:Nn \l__aTableau_bead_width_fp  { #1*\l__aTableau_bead_width_fp  }
    \fp_set:Nn \l__aTableau_box_col_dx_fp  { #1*\l__aTableau_box_col_dx_fp  }
    \fp_set:Nn \l__aTableau_box_row_dx_fp  { #1*\l__aTableau_box_row_dx_fp  }
    \fp_set:Nn \l__aTableau_box_wd_fp      { #1*\l__aTableau_box_wd_fp      }
    \fp_set:Nn \l__aTableau_separation_fp  { #1*\l__aTableau_separation_fp  }
    \fp_set:Nn \l__aTableau_tick_length_fp { #1*\l__aTableau_tick_length_fp }
    \fp_set:Nn \l__aTableau_xscale_fp      { #1 } % make scale persistent
}

% usage: \__aTableau_set_yscale:n {y-scale} : rescale the y-dimension
\cs_new_protected:Npn \__aTableau_set_yscale:n  #1
{
    \fp_set:Nn \l__aTableau_abacus_ht_fp   { #1*\l__aTableau_abacus_ht_fp   }
    \fp_set:Nn \l__aTableau_bead_height_fp { #1*\l__aTableau_bead_height_fp }
    \fp_set:Nn \l__aTableau_beam_height_fp { #1*\l__aTableau_beam_height_fp }
    \fp_set:Nn \l__aTableau_box_col_dy_fp  { #1*\l__aTableau_box_col_dy_fp  }
    \fp_set:Nn \l__aTableau_box_ht_fp      { #1*\l__aTableau_box_ht_fp      }
    \fp_set:Nn \l__aTableau_box_row_dy_fp  { #1*\l__aTableau_box_row_dy_fp  }
    \fp_set:Nn \l__aTableau_yscale_fp      { #1 } % make scale persistent
}

% ---------------------------------------------------------------------------
% tableau styles: english, french, australian and ukrainian - called from settings

% usage: \__aTableau_set_tableau_style:nnnn {col_dx}{col_dy}{row_dx}{row_dy}
% Set the style/convention for tableaus
\cs_new:Npn \__aTableau_set_tableau_style:nnnn #1 #2 #3 #4
{
    \fp_set:Nn \l__aTableau_box_col_dx_fp {#1*\l__aTableau_box_wd_fp}
    \fp_set:Nn \l__aTableau_box_col_dy_fp {#2*\l__aTableau_box_ht_fp}
    \fp_set:Nn \l__aTableau_box_row_dx_fp {#3*\l__aTableau_box_wd_fp}
    \fp_set:Nn \l__aTableau_box_row_dy_fp {#4*\l__aTableau_box_ht_fp}
}

\cs_new:Nn \__aTableau_set_tableau_style_english:
{
    \fp_set:Nn \l__aTableau_box_ht_fp {\l__aTableau_yscale_fp*0.5}
    \fp_set:Nn \l__aTableau_box_wd_fp {\l__aTableau_xscale_fp*0.5}
    \tl_set:Nn \l__aTableau_box_shape_tl {rectangle}
    \__aTableau_set_tableau_style:nnnn {1}{0}{0}{-1}
}

\cs_new:Nn \__aTableau_set_tableau_style_french:
{
    \fp_set:Nn \l__aTableau_box_ht_fp {\l__aTableau_yscale_fp*0.5}
    \fp_set:Nn \l__aTableau_box_wd_fp {\l__aTableau_xscale_fp*0.5}
    \tl_set:Nn \l__aTableau_box_shape_tl {rectangle}
    \__aTableau_set_tableau_style:nnnn {1}{0}{0}{1}
}

\cs_new:Nn \__aTableau_set_tableau_style_australian:
{
    \fp_set:Nn \l__aTableau_box_ht_fp {\l__aTableau_yscale_fp*0.7012} % 1/sqrt(2)
    \fp_set:Nn \l__aTableau_box_wd_fp {\l__aTableau_xscale_fp*0.7012}
    \tl_set:Nn \l__aTableau_box_shape_tl {diamond}
    \__aTableau_set_tableau_style:nnnn {0.5}{-0.5}{-0.5}{-0.5}
}

\cs_new:Nn \__aTableau_set_tableau_style_ukrainian:
{
    \fp_set:Nn \l__aTableau_box_ht_fp {\l__aTableau_yscale_fp*0.7012} % 1/sqrt(2)
    \fp_set:Nn \l__aTableau_box_wd_fp {\l__aTableau_xscale_fp*0.7012}
    \tl_set:Nn \l__aTableau_box_shape_tl {diamond}
    \__aTableau_set_tableau_style:nnnn {0.5}{0.5}{-0.5}{0.5}
}

% ---------------------------------------------------------------------------
% abacus conventions standard and traditional - called from settings

\cs_new:Nn \__aTableau_set_abacus_convention_standard:
{
    \bool_set_false:N \l__aTableau_traditional_bool
    \fp_set:Nn \l__aTableau_abacus_ht_fp     {\l__aTableau_yscale_fp*0.38} % ht and wd are used for the bead coordinates
    \fp_set:Nn \l__aTableau_abacus_wd_fp     {\l__aTableau_xscale_fp*0.38}
    \fp_set:Nn \l__aTableau_bead_height_fp   {\l__aTableau_abacus_ht_fp*0.95}  % height and width give the size of the beads
    \fp_set:Nn \l__aTableau_bead_width_fp    {\l__aTableau_abacus_wd_fp*0.95}
    \fp_set:Nn \l__aTableau_tick_length_fp   {\l__aTableau_xscale_fp*0.1}
    \tl_set:Nn \l__aTableau_bead_shape_tl    {circle}
    \tl_set:Nn \l__aTableau_tick_tl          {aTableauInner}
    \tl_set:Nn \l__aTableau_runner_tl        {aTableauInner}
    \cs_set_eq:NN \__aTableau_draw_abacus_end:nnnn \__aTableau_standard_end:nnnn
    \cs_set_eq:NN \__aTableau_draw_abacus_runner:nnn \__aTableau_standard_runner:nnn
}

\cs_new:Nn \__aTableau_set_abacus_convention_traditional:
{
    \bool_set_true:N \l__aTableau_traditional_bool
    \fp_set:Nn \l__aTableau_abacus_ht_fp     {\l__aTableau_yscale_fp*0.3}  % ht and wd are used for the bead coordinates
    \fp_set:Nn \l__aTableau_abacus_wd_fp     {\l__aTableau_xscale_fp*0.7}
    \fp_set:Nn \l__aTableau_bead_height_fp   {\l__aTableau_abacus_ht_fp*0.95 } % height and width give the size of the beads
    \fp_set:Nn \l__aTableau_bead_width_fp    {\l__aTableau_abacus_wd_fp*0.95 }
    \fp_set:Nn \l__aTableau_tick_length_fp   {\l__aTableau_xscale_fp*0.10}
    \tl_set:Nn \l__aTableau_bead_shape_tl    {rounded~rectangle}
    \tl_set:Nn \l__aTableau_tick_tl          {aTableauRod}
    \tl_set:Nn \l__aTableau_runner_tl        {aTableauRod}
    \cs_set_eq:NN \__aTableau_draw_abacus_end:nnnn \__aTableau_traditional_end:nnnn
    \cs_set_eq:NN \__aTableau_draw_abacus_runner:nnn \__aTableau_traditional_runner:nnn
}

% ---------------------------------------------------------------------------
% abacus styles north, south, east and west - called from settings

% usage: \__aTableau_set_abacus_style:nnnn {col_dx}{col_dy}{row_dx}{row_dy}
% Set the style/convention for abacuses
\cs_new:Npn \__aTableau_set_abacus_style:nnnn #1 #2 #3 #4
{
    \fp_set:Nn \l__aTableau_abacus_col_dx_fp {#1}
    \fp_set:Nn \l__aTableau_abacus_col_dy_fp {#2}
    \fp_set:Nn \l__aTableau_abacus_row_dx_fp {#3}
    \fp_set:Nn \l__aTableau_abacus_row_dy_fp {#4}
}

% ---------------------------------------------------------------------------
% usage: \__aTableau_set_delimiters:nn {left delimiter} {right delimiter}
\cs_new_protected:Npn \__aTableau_set_delimiters:nn #1 #2
{
    \tl_set:Nn \l__aTableau_left_delimiter_tl  #1
    \tl_set:Nn \l__aTableau_right_delimiter_tl #2
}

% usage: \__aTableau_set_multi_seq_key:nn {name} {value}
% Set up a partition for keys used with multishapes
\cs_new_protected:Npn \__aTableau_set_multi_shape_key:nn #1 #2
{
    \tl_if_in:nnTF {#2} {|}
    {
        % unpack the ribbons into \l__aTableau_multi#1_seq
        \seq_set_split:cnn {l__aTableau_multi#1_seq} {|} {#2}
    }
    {
        \__aTableau_set_partition:nn {#1} {#2}
    }
}

% usage: \__aTableau_set_multi_seq_key:nn {name} {value}
% Set up a sequence for keys used with multishapes
\cs_new_protected:Npn \__aTableau_set_multi_seq_key:nn #1 #2
{
    \tl_if_in:nnTF {#2} {|}
    {
        % unpack the ribbons into \l__aTableau_multi#1_seq
        \seq_set_split:cnn {l__aTableau_multi#1_seq} {|} {#2}
    }
    {
        \seq_set_from_clist:cn {l__aTableau_#1_seq} {#2}
    }
}

% usage: \__aTableau_set_multi_tl_key:nn {name} {value}
% Set up a token list for keys used with multishapes
\cs_new_protected:Npn \__aTableau_set_multi_tl_key:nn #1 #2
{
    \tl_if_in:nnTF {#2} {|}
    {
        % unpack the ribbons into \l__aTableau_multi#1_seq
        \seq_set_split:cnn {l__aTableau_multi#1_seq} {|} {#2}
    }
    {
        \tl_set:cn {l__aTableau_#1_tl} {#2}
    }
}

% usage: \__aTableau_set_abacus_ends:nn {top} {top}
% Set the abacus ends abacus_top and abacus_bottom and give an error
% message if the ends are not one of =,-,.,|,>
\cs_new_protected:Npn \__aTableau_set_abacus_ends:nn #1 #2
{
    \tl_set:Nn \l__aTableau_abacus_top_tl {#1}
    \str_if_in:nnF {-.>|\c_underscore_str*} {#1}
    { \msg_error:nne {aTableau} {unknown-abacus-end} {#1}}

    \tl_set:Nn \l__aTableau_abacus_bottom_tl {#2}
    \str_if_in:nnF {-.>|\c_underscore_str*} {#2}
    { \msg_error:nne {aTableau} {unknown-abacus-end} {#2}}
}

% ---------------------------------------------------------------------------
% command variants

\cs_generate_variant:Nn \fp_add:Nn {NV}
\cs_generate_variant:Nn \int_compare:nNnT {oNnT}
\cs_generate_variant:Nn \int_set:Nn {No}
\cs_generate_variant:Nn \seq_put_right:Nn {Nx}
\cs_generate_variant:Nn \seq_set_item:Nnn {NVx, Nnx, Nox}
\cs_generate_variant:Nn \seq_set_from_clist:Nn {co}
\cs_generate_variant:Nn \seq_set_split:Nnn {cnn}

\cs_generate_variant:Nn \__aTableau_abacus_bead:nnn {enV}
\cs_generate_variant:Nn \__aTableau_count_row:n {x}
\cs_generate_variant:Nn \__aTableau_draw_tableau:n {V}
\cs_generate_variant:Nn \__aTableau_define_html_colour:nn {no}
\cs_generate_variant:Nn \__aTableau_entry:n {x}
\cs_generate_variant:Nn \__aTableau_add_ribbon:nn {nV}
\cs_generate_variant:Nn \__aTableau_put_tikz_node:nnnnn {neVnV,noVno}
\cs_generate_variant:Nn \__aTableau_set_bead_coordinates:nnn {nVn, nVV, nnV, noo, non, nVo, noV, nno}
\cs_generate_variant:Nn \__aTableau_set_box_coordinates:nnn {nVV, nVn, noo, noV, nnV }
\cs_generate_variant:Nn \__aTableau_set_partition:nn {nV}
\cs_generate_variant:Nn \__aTableau_tikz_node:nnnn {nVno}
\cs_generate_variant:Nn \__aTableau_tl_put_right_braced:Nn { NV, Nx, No, Ne }

% ---------------------------------------------------------------------------
% utility functions

% usage: \__aTableau_tl_put_right_braced:Nn #1 #2
% Add braces around #2 and append to #1
\cs_new_protected:Nn \__aTableau_tl_put_right_braced:Nn
{
  \tl_put_right:Nn #1 { {#2} }
}

% ---------------------------------------------------------------------------
% expandable residue functions

% usage: \__aTableau_residue_A:nn
% Computes the residue of #1 mod #2. This is positive integer remainder when
% dividing by #2. As with \int_mod:n, the result is left in the input stream.
\cs_new_nopar:Npn \__aTableau_residue_A:nn #1 #2
{
    \int_compare:nNnTF {#1} > {0}
        { \int_mod:nn {#1} {#2} }
        { \int_eval:n {\int_mod:nn {#2 + \int_mod:nn {#1} {#2}} {#2}} }% \int_mod:nn is negative
}

% usage: \__aTableau_residue_C:nn
% Computes the residue of #1 in affine type C. When e=3 this residue pattern is
%    0 1 2 3 2 1 0 1 2 3 2 1 0 1 ...
% As with \int_mod:n, the result is left in the input stream.
\cs_new_nopar:Npn \__aTableau_residue_C:nn #1 #2
{
    \int_compare:nNnTF {#1} < {0}
    { % #1 is negative
        \int_compare:nNnTF {\int_mod:nn {-1*#1} {2*#2}} < {#2}
        { \int_eval:n { \int_mod:nn {-1*#1} {2*#2} } }
        { \int_eval:n { 2*#2 - \int_mod:nn {-1*#1} {2*#2}       } }
    }
    { % #1 is non-negative
        \int_compare:nNnTF {\int_mod:nn {#1} {2*#2}} > {#2}
          { \int_eval:n { 2*#2 - \int_mod:nn {#1} {2*#2} } }
          { \int_eval:n { \int_mod:nn {#1} {2*#2}        } }
    }
}

% usage: \__aTableau_residue_AA:nn
% Computes the residue of #1 in twisted type A. When e=3 this residue pattern is
%    0 1 2 3 3 2 1 0 1 2 3 3 2 1 0 1 ...
% As with \int_mod:n, the result is left in the input stream.
\cs_new_nopar:Npn \__aTableau_residue_AA:nn #1 #2
{
    \int_compare:nNnTF {#1} < {0}
    { % #1 is negative
        \int_compare:nNnTF {\int_mod:nn {#1} {2*#2+1}} < {-#2}
        { \int_eval:n { 2*#2+1 + \int_mod:nn {#1} {2*#2+1}} }
        { \int_eval:n { \int_mod:nn {-#1} {2*#2+1} } }
    }
    { % #1 is non-negative
        \int_compare:nNnTF {\int_mod:nn {#1} {2*#2+1}} > {#2}
          { \int_eval:n { 2*#2+1 - \int_mod:nn {#1} {2*#2+1} } }
          { \int_eval:n { \int_mod:nn {#1} {2*#2+1}      } }
    }
}

% usage: \__aTableau_residue_D:nn
% Computes the residue of #1 in affine type D. When e=3 this residue pattern is
%    0 1 2 3 3 2 1 0 0 1 2 3 3 2 1 0 0 1 ...
% As with \int_mod:n, the result is left in the input stream.
\cs_new_nopar:Npn \__aTableau_residue_DD:nn #1 #2
{
    \int_compare:nNnTF {#1} < {0}
    { % #1 is negative
        \int_compare:nNnTF {\int_mod:nn {#1} {2*#2+2}} < {-#2}
        { \int_eval:n {2*#2+1 + \int_mod:nn {#1} {2*#2+2}} }
        { \int_eval:n { -\int_mod:nn {#1} {2*#2+2} } }
    }
    { % #1 is non-negative
        \int_compare:nNnTF {\int_mod:nn {#1} {2*#2+2}} > {#2}
          { \int_eval:n {2*#2+1- \int_mod:nn {#1} {2*#2+2} } }
          { \int_eval:n { \int_mod:nn {#1} {2*#2+2} } }
    }
}

% ---------------------------------------------------------------------------
% Parsing input

% \seq_set_split:Nnn does not respect braces around singleton entries
% such as in { 1345, 8{10}, {11}, {12} }, with the result that {11}
% and {12} are treated as {1}{1} and {1}{2}, respectively. As this is
% not what we want, we use \peek_charcode:NTF to do this ourselves. As a bonus,
% this means that comma-separated style specifications for nodes do not need to
% be enclosed in braces as the surrounding square brackets, [...], are
% sufficient to capture them in \__aTableau_record_style:nn.
\cs_new_protected:Npn \__aTableau_peek_tableau:w
{
    \peek_remove_spaces:n { % ignore spaces
        \peek_charcode_remove:NTF ,
        {
            % record the column index in the shape for drawing the border
            \seq_put_right:NV \l__aTableau_shape_seq \l__aTableau_col_int
            % increment the row index
            \int_incr:N \l__aTableau_row_int
            % reset the column index, and update the skew shape for shifted tableau
            \bool_if:NTF \l__aTableau_shifted_bool
            {
                \seq_put_right:No \l__aTableau_skew_seq {\int_use:N \l__aTableau_row_int}
                \int_set_eq:NN \l__aTableau_col_int \l__aTableau_row_int
            }
            {
                \int_set:Nn \l__aTableau_col_int { 0\seq_item:Nn \l__aTableau_skew_seq {\l__aTableau_row_int+1}}
            }
            % look for the next entry
            \__aTableau_peek_tableau:w
         }
         {
            \__aTableau_peek_style:nw {draw_entry:nn}
         }
    }
}

% usage: \__aTableau_peek_style:nw {command suffix}
% Read the next entry in the input sequence, with any optional style given by
% a * or [...], and then pass this data to the command \__aTableau_#1.
\cs_new_protected:Npn \__aTableau_peek_style:nw #1
{
    \peek_remove_spaces:n { % ignore spaces
      \peek_charcode:NTF [
        { \use:c{__aTableau_#1} } % read custom style
        {
          \peek_charcode_remove:NTF *
            { \use:c{__aTableau_#1} [\l__aTableau_starstyle_tl] } % add star style
            { \use:c{__aTableau_#1} [] }% no style
        }
    }
}

% usage: \__aTableau_record_style:nn [style] {part}
% To parse the bead specifications used by \Abacus we need to accept the
% a "bead specification" of the following form
%    [style]m^k_txt
% where the [style] could simply be a *, the m is an integer, a part of the
% partition being constructed, the k its exponent and txt is the text for the
% node.  Except for m, all of of these components are optional, with the
% exponent being 1 if it is omitted. As is customary, the order of the
% superscript and subscript is interchangeable. To parse these expressions we
% first use \__aTableau_peek_style:nw to strip off the style specification, via
% \__aTableau_record_style:nn, and then pass between \__aTableau_peek_beads:nw
% and \__aTableau_record:nn to look ahead for the characters ^ and _ to decide
% which of the following token lists the next character is added to:
\cs_new_protected:Npn \__aTableau_record_style:nn [#1] #2
{
    % clear the bead token lists
    \tl_set:Nn \l__aTableau_capture_style_tl {#1}
    \tl_set:Nn \l__aTableau_capture_part_tl  {#2}
    \tl_clear:N \l__aTableau_capture_exp_tl
    \tl_clear:N \l__aTableau_capture_txt_tl
    \__aTableau_peek_beads:nw {part}
}

% usage: \__aTableau_peek_beads:nw {type} {stream}
% Look for a caret or an underscore and pass on to \__aTableau_capture_bead:nn
\cs_new_protected:Npn \__aTableau_peek_beads:nw #1
{
    \peek_remove_spaces:n { % ignore spaces
        \peek_charcode_remove:NTF ,
        {
            \__aTableau_record_bead:
            % increment bead number and capture the specifications for the next bead
            \int_incr:N \l__aTableau_r_int
            \__aTableau_peek_style:nw {record_style:nn}
        }
        {
          \peek_charcode_remove:NTF _
          { \__aTableau_capture_bead:nn {txt} {} }
          {
            \peek_charcode_remove:NTF ^ { \__aTableau_capture_bead:nn {exp} {}}
                                        { \__aTableau_capture_bead:nn {#1} }
          }
        }
    }
}

% usage: \__aTableau_capture_bead:nn {place} {contents}
% Records the bead data. Here #1 is one of part, exp or txt
\cs_new_protected:Npn \__aTableau_capture_bead:nn #1 #2
{
    \quark_if_recursion_tail_stop_do:nn {#2}
    {
        \__aTableau_record_bead:
    }
    \tl_put_right:cn {l__aTableau_capture_#1_tl} {#2}
    \__aTableau_peek_beads:nw {#1}
}

% usage: \__aTableau_record_bead:
% Records the bead data. Here #1 is one of part, exp or txt
\cs_new_protected:Nn \__aTableau_record_bead:
{
  % add the data to the sequences \l__aTableau_capture_exp_tl times
  \tl_if_empty:NT \l__aTableau_capture_exp_tl { \tl_set:Nn \l__aTableau_capture_exp_tl {1}}
  \int_step_inline:nn { \l__aTableau_capture_exp_tl }
  {
      \seq_put_right:NV \l__aTableau_rcs_seq \l__aTableau_capture_part_tl
      \seq_put_right:NV \l__aTableau_texts_seq  \l__aTableau_capture_txt_tl
      \seq_put_right:NV \l__aTableau_styles_seq \l__aTableau_capture_style_tl
  }
}

% usage: \__aTableau_count_entries:nn [style] {entry}
% Count the number of entries in the row, storing the result in c_int
\cs_new_protected:Npn \__aTableau_count_entries:nn [#1] #2
{
    % exit when we reach the end of the row
    \quark_if_recursion_tail_stop:n {#2}

    \int_incr:N \l__aTableau_c_int
    \__aTableau_peek_style:nw {count_entries:nn}
}

% usage: \__aTableau_count_row:n {entries}
\cs_new_protected:Npn \__aTableau_count_row:n #1
{
    \__aTableau_peek_style:nw {count_entries:nn} #1 \q_recursion_tail \q_recursion_stop
}

% ---------------------------------------------------------------------------
% creating custom diagrams for the key entries=...

% usage: \__aTableau_compute_conjugate_partition:N {seq}
% compute the conjugate partition of <seq> and store in \l__aTableau_conjugate_seq
\cs_new_protected:Npn \__aTableau_compute_conjugate_partition:N #1
{
    \seq_clear:N \l__aTableau_conjugate_seq
    \int_zero:N \l_tmpa_int % previous part
    \int_set:No \l__aTableau_r_int {\seq_count:N #1}
    \int_while_do:nn {\l__aTableau_r_int > 0}
    {
        \int_set:No \l_tmpb_int {\seq_item:NV #1 \l__aTableau_r_int }
        \int_step_inline:nn { \l_tmpb_int - \l_tmpa_int }
        {
            \seq_put_right:NV \l__aTableau_conjugate_seq \l__aTableau_r_int
        }
        \int_set_eq:NN \l_tmpa_int \l_tmpb_int
        \int_decr:N \l__aTableau_r_int
    }
}

% usage: \__aTableau_set_partition:nn {shape|skew|cover} {csv for partition}
% This function allows the partition to be given either as a comma separated
% list of integers, or as a comma separated list of integers with exponents
% giving the multiplicity of each part. For example, 4,3,3,3,2 and 4,3^3,2
% are both supported.
% TODO: ?? Remove the hack below using \seq_set_split:Nnn and rewrite using quarks
\cs_new_protected:Npn \__aTableau_set_partition:nn #1 #2
{
  \seq_clear:c {l__aTableau_#1_seq}
  \clist_map_inline:nn {#2} { \__aTableau_add_to_partition:nn {#1} {##1} }
}

% add parts to the partition \l__aTableau_#1_seq given input
% of the form k or k^r
\cs_new_protected:Npn \__aTableau_add_to_partition:nn #1 #2
{
    % split #1 on ^: the trailing ^1 sets the exponent to 1 if it's omitted
    \seq_set_split:Nnn \l_tmpa_seq {^} {#2 ^1}
    % given k or k^r, set \l_tmpa_int=k and \l_tmpb_int=r, where r=1 with input k
    \seq_pop_left:NN  \l_tmpa_seq \l_tmpa_tl % part
    \seq_pop_left:NN \l_tmpa_seq \l_tmpb_tl  % exponent
    % now add \l_tmpa_int to \l__aTableau_#1_seq b times
    \int_step_inline:nn {\l_tmpb_tl} { \seq_put_right:ce {l__aTableau_#1_seq} {\l_tmpa_tl} }
}

% usage: \__aTableau_diagram_for_shape:N {partition}
% Young diagrams are drawn using the \Tableau command by generating
% a sequence of ~ for the partition. For example,
% it replaces the sequence 3,2,2,1 with
% the dot-sequence ~~~,~~,~~,~,
\cs_new_protected:Npn \__aTableau_diagram_for_shape:N #1
{
    \tl_clear:N \l__aTableau_entries_tl
    \tl_set:Nn \l_tmpb_tl {}
    \seq_map_inline:Nn #1
    {
        \tl_put_right:No \l__aTableau_entries_tl \l_tmpb_tl
        \tl_put_right:Nx \l__aTableau_entries_tl  { \prg_replicate:nn {##1} {{{~}}} }
        \tl_set:Nn \l_tmpb_tl {,}
    }
}

% usage: \__aTableau_shape_to_content:Nn {partition sequence}
% Return the content sequence for a tableau of this shape
\cs_new_protected:Npn \__aTableau_shape_to_content:N #1
{
    \tl_clear:N \l__aTableau_entries_tl
    \int_zero:N \l__aTableau_row_int
    \tl_set:Nn \l_tmpb_tl {}
    \seq_map_inline:Nn #1
    {
        \tl_put_right:No \l__aTableau_entries_tl \l_tmpb_tl
        \int_incr:N \l__aTableau_row_int
        \int_step_inline:nn {##1}
        {
            \int_set:Nn \l_tmpa_int { \l__aTableau_charge_int + ####1 - \l__aTableau_row_int + 0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_row_int }
            \int_compare:nNnTF {\l_tmpa_int} < {0}
            { \__aTableau_tl_put_right_braced:Ne \l__aTableau_entries_tl {\exp_not:N\shortminus\int_eval:n{-\l_tmpa_int}} }
            { \__aTableau_tl_put_right_braced:No \l__aTableau_entries_tl {\int_use:N \l_tmpa_int} }
        }
        \tl_set:Nn \l_tmpb_tl {,}
    }
}

% usage: \__aTableau_shape_to_last:N {partition sequence}
% Diagrams with entries=last are drawn using the \Tableau command
\cs_new_protected:Npn \__aTableau_shape_to_last:N #1
{
    % compute conjugate of #1 as \l__aTableau_conjugate_seq
    %\__aTableau_compute_conjugate_partition:N #1

    % initialise \l_tmpc_seq -- should not be necessary but otherwise \seq_item:NV fails
    \seq_clear:N \l_tmpc_seq % construct the tableau in this sequence and then decant
    \seq_map_inline:Nn #1 {\seq_put_right:Nn \l_tmpc_seq {}}

    % value of last entry added to the tableau
    \int_set:Nn  \l__aTableau_c_int {\l__aTableau_charge_int}

    % length of the first row of the partition/seq #1
    \int_set:Nn \l__aTableau_row_int {0\seq_item:Nn #1 {1}}

    \int_step_inline:nn {\l__aTableau_row_int}  % ##1 is the column index
    {
        \int_zero:N \l__aTableau_r_int          % r_int is the row index
        \seq_map_inline:Nn #1                   % ####1 is the rth entry of the partition #1
        {
            \int_incr:N \l__aTableau_r_int
            \tl_set:Nn \l_tmpa_tl { 0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_r_int}
            \bool_if:nT { \int_compare_p:nNn {##1} > {\l_tmpa_tl} && \int_compare_p:nNn {\l_tmpa_tl+####1+1} > {##1}}
            {
                \int_incr:N \l__aTableau_c_int
                \tl_set:No \l_tmpa_tl { \seq_item:NV \l_tmpc_seq \l__aTableau_r_int }
                \__aTableau_tl_put_right_braced:No \l_tmpa_tl {\int_use:N \l__aTableau_c_int}
                \seq_set_item:NVx \l_tmpc_seq \l__aTableau_r_int {\l_tmpa_tl}
            }
        }
    }

    % finally, unpack \l_tmpc_tl into l__aTableau_entries_tl
    \tl_clear:N \l__aTableau_entries_tl
    \tl_set:Nn \l_tmpb_tl {}
    \seq_map_inline:Nn \l_tmpc_seq
    {
        \tl_put_right:No \l__aTableau_entries_tl \l_tmpb_tl
        \tl_put_right:Nn \l__aTableau_entries_tl {##1}
        \tl_set:Nn \l_tmpb_tl {,}
    }
}

% usage: \__aTableau_shape_to_first:N {partition sequence}
% Diagrams with entries=first are drawn using the \Tableau command
\cs_new_protected:Npn \__aTableau_shape_to_first:N #1
{
    \tl_clear:N \l__aTableau_entries_tl
    \int_set:Nn \l__aTableau_r_int {\l__aTableau_charge_int}
    \tl_set:Nn \l_tmpb_tl {}
    \seq_map_inline:Nn #1
    {
        \tl_put_right:No \l__aTableau_entries_tl \l_tmpb_tl
        \int_step_inline:nn {##1}
        {
            \int_incr:N \l__aTableau_r_int
            \__aTableau_tl_put_right_braced:NV \l__aTableau_entries_tl \l__aTableau_r_int
        }
        \tl_set:Nn \l_tmpb_tl {,}
    }
    \seq_put_right:No \l__aTableau_charge_seq {\int_use:N\l__aTableau_r_int}
}

% usage: \__aTableau_shape_to_hook:N {partition_seq}
% Return a string for the hook length tableau. Some extra care needed for
% shifted tableau, which use a different definition of hook length
\cs_new_protected:Npn \__aTableau_shape_to_hook:N #1
{
    % now construct the hook tableau
    \tl_clear:N \l__aTableau_entries_tl
    \bool_if:NTF \l__aTableau_shifted_bool
    {   % shifted tableau
        % initialise l_tmpc_seq because we will construct the entries in it and unpack at the end
        \seq_clear:N \l_tmpc_seq
        \seq_map_inline:Nn #1 {\seq_put_right:Nn \l_tmpc_seq {}}
        \int_set:Nn \l__aTableau_col_int { 0\seq_item:Nn #1 {1} } % length of first row
        \int_step_inline:nn { \l__aTableau_col_int }
        {   % add hooks column by column, starting by determining the rows in columns ##1
            \int_zero:N \l__aTableau_row_int
            \seq_map_inline:Nn #1
            {
                \bool_if:nT { \int_compare_p:nNn {##1} > {\l__aTableau_row_int} && \int_compare_p:nNn {####1+\l__aTableau_row_int+1} > {##1} }
                {
                    \int_incr:N \l__aTableau_row_int
                }
            }
            % record length of row col+1
            \int_set:Nn \l_tmpa_int {0\seq_item:Nn #1 {##1+1}}
            % loop through rows 1,...,row and add the hook lengths
            \int_step_inline:nn {\l__aTableau_row_int}
            {
                \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpc_seq {####1} }
                \tl_set:Nx \l_tmpb_tl { \int_eval:n {\seq_item:Nn #1 {####1}  + \l__aTableau_row_int - ##1 + \l_tmpa_int  } }
                \__aTableau_tl_put_right_braced:NV \l_tmpa_tl \l_tmpb_tl
                \seq_set_item:Nnx \l_tmpc_seq {####1} {\l_tmpa_tl}
            }
        }
        \tl_set:Nx \l__aTableau_entries_tl { \seq_use:Nn \l_tmpc_seq {,} }
    }
    { % unshifted (although could be skew, when all bets are off...)
      % compute conjugate of #1 as \l__aTableau_conjugate_seq
      \int_zero:N \l__aTableau_row_int
      \tl_set:Nn \l_tmpb_tl {}
      \__aTableau_compute_conjugate_partition:N #1
      \seq_map_inline:Nn #1
      {
        \tl_put_right:No \l__aTableau_entries_tl \l_tmpb_tl
        \int_incr:N \l__aTableau_row_int
        \int_step_inline:nn {##1}
        {
            \tl_set:Ne \l_tmpc_tl { \int_eval:n {##1+\seq_item:Nn \l__aTableau_conjugate_seq {####1} -\l__aTableau_row_int-####1+1} }
            \__aTableau_tl_put_right_braced:NV \l__aTableau_entries_tl \l_tmpc_tl
        }
        \tl_set:Nn \l_tmpb_tl {,}
      }
    }
}

% usage: \__aTableau_shape_to_residue:N {partition sequence}
% Residue diagrams are drawn using the \Tableau command
\cs_new_protected:Npn \__aTableau_shape_to_residue:N #1
{
    \tl_clear:N \l__aTableau_entries_tl
    \int_zero:N \l__aTableau_row_int
    \tl_set:Nn \l_tmpb_tl {}
    \seq_map_inline:Nn #1
    {
        \tl_put_right:No \l__aTableau_entries_tl \l_tmpb_tl
        \int_incr:N \l__aTableau_row_int
        \int_step_inline:nn {##1}
        {
            \int_set:Nn \l_tmpa_int { \l__aTableau_charge_int + ####1 - \l__aTableau_row_int }
            \int_add:Nn \l_tmpa_int { + 0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_row_int }
            \tl_set:Nx \l_tmpa_tl { \__aTableau_residue:nn { \l_tmpa_int } {\l__aTableau_e_int} }
            \__aTableau_tl_put_right_braced:Nx \l__aTableau_entries_tl { \l_tmpa_tl }
        }
        \tl_set:Nn \l_tmpb_tl {,}
    }
}

% ---------------------------------------------------------------------------
% draw diagram border

% usage: \__aTableau_draw_border:nn {name of sequence} {name of border style}
% The name of the sequence is cover, skew, or shape.
\cs_new_protected:Npn \__aTableau_draw_border:nn #1 #2
{
    \int_zero:N \l__aTableau_r_int  % row index
    \int_zero:N \l__aTableau_c_int  % column index
    \tl_clear:N \l__aTableau_border_tl % will hold the border

    \seq_map_inline:cn {l__aTableau_#1_seq}
    {
      % compute the endpoints of this row for skew and cover shapes
      \str_case:nn {#1}
      {
        {cover}
            { % left column depends on l__aTableau_shape_seq
              \int_set:Nn \l__aTableau_c_int { 0\seq_item:Nn \l__aTableau_shape_seq {\l__aTableau_r_int+1}}
              \bool_if:nT { \l__aTableau_tabloid_bool && \int_compare_p:nNn {\l__aTableau_r_int} > {0} }
              {
                  \int_set:Nn \l__aTableau_c_int { \int_min:nn {\l__aTableau_c_int} {0\seq_item:NV \l__aTableau_shape_seq \l__aTableau_r_int} }
              }
            }
        {shape}
            { % left column depends on l__aTableau_skew_seq
              \int_set:Nn \l__aTableau_c_int { 0\seq_item:Nn \l__aTableau_skew_seq {\l__aTableau_r_int+1}}
              \bool_if:nT { \l__aTableau_tabloid_bool && \int_compare_p:nNn {\l__aTableau_r_int} > {0} }
              {
                  \int_set:Nn \l__aTableau_c_int { \int_min:nn {\l__aTableau_c_int} {0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_r_int} }
              }
            }
      }

      \__aTableau_set_box_coordinates:nVV {a} \l__aTableau_r_int \l__aTableau_c_int
      \__aTableau_set_box_coordinates:nVn {b} \l__aTableau_r_int {##1}
      \bool_if:nTF { \int_compare_p:n {\l__aTableau_r_int = 0} || \l__aTableau_tabloid_bool }
      {
        % add line along "top" of the row
        \tl_put_right:Nx \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)}
        \__aTableau_add_row_ends:
      }
      {
        % add lines for second and later rows
        \tl_put_left:Nx  \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--}
        \tl_put_right:Nx \l__aTableau_border_tl {--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)}
        \__aTableau_add_row_ends:
      }
      \int_incr:N \l__aTableau_r_int
    }

    % draw the border that we have constructed
    \tl_if_empty:NF \l__aTableau_border_tl
    {
      % fill in the last line
      \bool_if:nT { \l__aTableau_tabloid_bool && \int_compare_p:nNn {\l__aTableau_r_int} > {0} }
      {
          \int_set:Nn \l__aTableau_c_int { 0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_r_int }
          \__aTableau_set_box_coordinates:nVV {a} \l__aTableau_r_int \l__aTableau_c_int
      }
      \tl_put_right:Nx \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)}

      % draw the border
      \draw[aTableau/#2] \l__aTableau_border_tl;
    }
}

% usage: \__aTableau_remove_dotted_tableau_rows:
% Poke some holes in the border for the rows in dotted_rows_seq
\cs_new_protected:Nn \__aTableau_remove_dotted_tableau_rows:
{
    % To collect repeated rows in dotted_rows_seq
    % we use \seq_map_inline:Nn and then compare ##1 with \l__aTableau_r_int to
    % determine if this is a new row.

    % take a copy of \l__aTableau_dotted_rows_seq so that the pop_left's below
    % do not destroy it
    \seq_set_eq:NN \l_tmpb_seq \l__aTableau_dotted_rows_seq
    \bool_do_until:nn { \seq_if_empty_p:N \l_tmpb_seq }
    {
        \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
        \int_set:Nn \l__aTableau_row_int {\l_tmpa_tl}
        \int_set:Nn \l__aTableau_r_int {\l__aTableau_row_int+1}

        % LaTeX3 does not provide \seq_if_in_p:NN, so ...
        \bool_set_true:N \l_tmpa_bool
        \bool_do_while:nn { \l_tmpa_bool }
        {
            \int_set:Nn \l_tmpa_int {0\seq_item:Nn \l_tmpb_seq 1}
            \int_compare:nNnTF {\l__aTableau_r_int} = {\l_tmpa_int}
            {
              \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
              \int_incr:N \l__aTableau_r_int
            }
            { \bool_set_false:N \l_tmpa_bool }
        }

        % We want to blank out the rows between the four coordinates
        %      a=(row,tmpa) .... b=(row,col)
        %      |                 |
        %      c=(r,tmpb) ....   d=(r,c)

        % set tmpa and tmpb to the column index for rows row and r, respectively
        \int_set:No \l__aTableau_col_int { 0\seq_item:NV \l__aTableau_shape_seq \l__aTableau_row_int } % mu_row
        \int_set:No \l__aTableau_c_int   { 0\seq_item:NV \l__aTableau_shape_seq \l__aTableau_r_int }   % mu_r

        % shift in col-direction
        \fp_set:Nn \l__aTableau_xa_fp {\l__aTableau_box_col_dx_fp}
        \fp_set:Nn \l__aTableau_ya_fp {\l__aTableau_box_col_dy_fp}

        % shift in row-direction
        \fp_set:Nn \l__aTableau_xb_fp {\l__aTableau_box_row_dx_fp}
        \fp_set:Nn \l__aTableau_yb_fp {\l__aTableau_box_row_dy_fp}

        % a draw between the coordinates a, b, c, d
        \__aTableau_set_box_coordinates:noo {l} {\l__aTableau_row_int-1} {\l__aTableau_col_int-1} % point b

        \draw[aTableau/clearBoxes]
          (\fp_eval:n{\l__aTableau_xl_fp+0.58*\l__aTableau_xa_fp-0.42*\l__aTableau_xb_fp},
           \fp_eval:n{\l__aTableau_yl_fp+0.58*\l__aTableau_ya_fp-0.42*\l__aTableau_yb_fp})
          --++(\fp_eval:n{(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_xb_fp},
               \fp_eval:n{(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_yb_fp})
          --++(\fp_eval:n{(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_xa_fp},
               \fp_eval:n{(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_ya_fp})
          --++(\fp_eval:n{-0.12*\l__aTableau_xb_fp}, \fp_eval:n{-0.12*\l__aTableau_yb_fp})
          --++(\fp_eval:n{-(0.15+\l__aTableau_c_int)*\l__aTableau_xa_fp}, \fp_eval:n{-(0.15+\l__aTableau_c_int)*\l__aTableau_ya_fp})
          --++(\fp_eval:n{(\l__aTableau_row_int-\l__aTableau_r_int+0.12)*\l__aTableau_xb_fp},
               \fp_eval:n{(\l__aTableau_row_int-\l__aTableau_r_int+0.12)*\l__aTableau_yb_fp})
          --cycle
        ;

        % finally, we need to add dots between b and d
        \draw[aTableau/dottedLine]
          (\fp_eval:n{0.5*\l__aTableau_xa_fp-0.5*\l__aTableau_xb_fp+\l__aTableau_xl_fp},
           \fp_eval:n{0.5*\l__aTableau_ya_fp-0.5*\l__aTableau_yb_fp+\l__aTableau_yl_fp})
          --++(\fp_eval:n{(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_xa_fp+(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_xb_fp},
               \fp_eval:n{(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_ya_fp+(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_yb_fp})
        ;

        % and between a and c, which is trickier as the skew shape plays a role
        \int_set:No \l_tmpa_int { \int_eval:n {0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_row_int} }
        \int_set:No \l_tmpb_int { \int_eval:n {\int_max:nn{\l_tmpa_int}{0\seq_item:NV \l__aTableau_skew_seq \l__aTableau_r_int} }}
        \int_decr:N \l__aTableau_row_int
        \int_decr:N \l__aTableau_r_int
        \draw[aTableau/dottedLine]
          (\fp_eval:n{\l__aTableau_x_fp+\l_tmpa_int*\l__aTableau_xa_fp+\l__aTableau_row_int*\l__aTableau_xb_fp},
           \fp_eval:n{\l__aTableau_y_fp+\l_tmpa_int*\l__aTableau_ya_fp+\l__aTableau_row_int*\l__aTableau_yb_fp})
           --(\fp_eval:n{\l__aTableau_x_fp+\l_tmpb_int*\l__aTableau_xa_fp+\l__aTableau_r_int*\l__aTableau_xb_fp},
              \fp_eval:n{\l__aTableau_y_fp+\l_tmpb_int*\l__aTableau_ya_fp+\l__aTableau_r_int*\l__aTableau_yb_fp})
        ;

        \bool_if:nT { \int_compare_p:nNn {\l_tmpa_int}>{0} && \l__aTableau_skew_border_bool }
        {
          \draw[aTableau/dottedLine,draw=\l__aTableau_skew_border_tl]
            (\fp_eval:n{\l__aTableau_x_fp+\l__aTableau_row_int*\l__aTableau_xb_fp},
             \fp_eval:n{\l__aTableau_y_fp+\l__aTableau_row_int*\l__aTableau_yb_fp})
            --(\fp_eval:n{\l__aTableau_x_fp+\l__aTableau_r_int*\l__aTableau_xb_fp},
               \fp_eval:n{\l__aTableau_y_fp+\l__aTableau_r_int*\l__aTableau_yb_fp})
          ;
        }
    }
}

% usage: \__aTableau_remove_dotted_tableau_cols:
% Poke some holes in the border for the cols in dotted_rows_seq
\cs_new_protected:Nn \__aTableau_remove_dotted_tableau_cols:
{
    % To collect repeated columns in dotted_cols_seq
    % we use \seq_map_inline:Nn and then compare ##1 with \l__aTableau_c_int to
    % determine if this is a new column.

    % conjugate partition and skew shape
    \__aTableau_compute_conjugate_partition:N \l__aTableau_skew_seq
    \seq_set_eq:NN \l_tmpa_seq \l__aTableau_conjugate_seq % \l_tmpa_seq is the conjugate skew
    \__aTableau_compute_conjugate_partition:N \l__aTableau_shape_seq % conjugate shape

    % take a copy of \l__aTableau_dotted_cols_seq so that the pop_left's below
    % do not destroy it
    \seq_set_eq:NN \l_tmpb_seq \l__aTableau_dotted_cols_seq
    \bool_do_until:nn { \seq_if_empty_p:N \l_tmpb_seq }
    {
        \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
        \int_set:Nn \l__aTableau_col_int {\l_tmpa_tl}
        \int_set:Nn \l__aTableau_c_int {\l__aTableau_col_int+1}

        % LaTeX3 does not provide \seq_if_in_p:NN, so ...
        \bool_set_true:N \l_tmpa_bool
        \bool_do_while:nn { \l_tmpa_bool }
        {
            \int_set:Nn \l_tmpa_int {0\seq_item:Nn \l_tmpb_seq 1}
            \int_compare:nNnTF {\l__aTableau_c_int} = {\l_tmpa_int}
            {
              \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
              \int_incr:N \l__aTableau_c_int
            }
            { \bool_set_false:N \l_tmpa_bool }
        }

        % We want to blank out the rows between the four coordinates
        %      a=(row,tmpa) .... b=(r,tmpb)
        %      |                 |
        %      c=(row,col) ....  d=(r,c)

        % set row and r to the row indices for col and c, respectively
        \int_set:No \l__aTableau_row_int { 0\seq_item:NV \l__aTableau_conjugate_seq \l__aTableau_col_int } % mu_row
        \int_set:No \l__aTableau_r_int   { 0\seq_item:NV \l__aTableau_conjugate_seq \l__aTableau_c_int }   % mu_r

        % shift in col-direction
        \fp_set:Nn \l__aTableau_xa_fp {\l__aTableau_box_col_dx_fp}
        \fp_set:Nn \l__aTableau_ya_fp {\l__aTableau_box_col_dy_fp}

        % shift in row-direction
        \fp_set:Nn \l__aTableau_xb_fp {\l__aTableau_box_row_dx_fp}
        \fp_set:Nn \l__aTableau_yb_fp {\l__aTableau_box_row_dy_fp}

        % a draw between the coordinates a, b, c, d
        \__aTableau_set_box_coordinates:noo {l} {\l__aTableau_row_int-1} {\l__aTableau_col_int-1} % point b

        \draw[aTableau/clearBoxes]
          (\fp_eval:n{\l__aTableau_xl_fp-0.42*\l__aTableau_xa_fp+0.58*\l__aTableau_xb_fp},
           \fp_eval:n{\l__aTableau_yl_fp-0.42*\l__aTableau_ya_fp+0.58*\l__aTableau_yb_fp})
          --++(\fp_eval:n{(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_xa_fp},
               \fp_eval:n{(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_ya_fp})
          --++(\fp_eval:n{(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_xb_fp},
               \fp_eval:n{(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_yb_fp})
          --++(\fp_eval:n{-0.12*\l__aTableau_xa_fp}, \fp_eval:n{-0.12*\l__aTableau_ya_fp})
          --++(\fp_eval:n{-(0.15+\l__aTableau_r_int)*\l__aTableau_xb_fp}, \fp_eval:n{-(0.15+\l__aTableau_r_int)*\l__aTableau_yb_fp})
          --++(\fp_eval:n{(\l__aTableau_col_int-\l__aTableau_c_int+0.12)*\l__aTableau_xa_fp},
               \fp_eval:n{(\l__aTableau_col_int-\l__aTableau_c_int+0.12)*\l__aTableau_ya_fp})
          --cycle
        ;

        % finally, we need to add dots between b and d
        \draw[aTableau/dottedLine]
          (\fp_eval:n{\l__aTableau_xl_fp-0.5*\l__aTableau_xa_fp+0.5*\l__aTableau_xb_fp},
           \fp_eval:n{\l__aTableau_yl_fp-0.5*\l__aTableau_ya_fp+0.5*\l__aTableau_yb_fp})
          --++(\fp_eval:n{(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_xb_fp+(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_xa_fp},
               \fp_eval:n{(\l__aTableau_r_int-\l__aTableau_row_int)*\l__aTableau_yb_fp+(\l__aTableau_c_int-\l__aTableau_col_int)*\l__aTableau_ya_fp})
        ;

        % and between a and c, which is trickier as the skew shape plays a role
        \int_set:No \l_tmpa_int { \int_eval:n {0\seq_item:NV \l_tmpa_seq \l__aTableau_col_int} }
        \int_set:No \l_tmpb_int { \int_eval:n {\int_max:nn{\l_tmpa_int}{0\seq_item:NV \l_tmpa_seq \l__aTableau_c_int} }}
        \int_decr:N \l__aTableau_col_int
        \int_decr:N \l__aTableau_c_int
        \draw[aTableau/dottedLine]
          (\fp_eval:n{\l__aTableau_x_fp+\l_tmpa_int*\l__aTableau_xb_fp+\l__aTableau_col_int*\l__aTableau_xa_fp},
           \fp_eval:n{\l__aTableau_y_fp+\l_tmpa_int*\l__aTableau_yb_fp+\l__aTableau_col_int*\l__aTableau_ya_fp})
           --(\fp_eval:n{\l__aTableau_x_fp+\l_tmpb_int*\l__aTableau_xb_fp+\l__aTableau_c_int*\l__aTableau_xa_fp},
              \fp_eval:n{\l__aTableau_y_fp+\l_tmpb_int*\l__aTableau_yb_fp+\l__aTableau_c_int*\l__aTableau_ya_fp})
        ;

        \bool_if:nT { \int_compare_p:nNn {\l_tmpa_int}>{0} && \l__aTableau_skew_border_bool }
        {
          \draw[aTableau/dottedLine,draw=\l__aTableau_skew_border_tl]
            (\fp_eval:n{\l__aTableau_x_fp+\l__aTableau_col_int*\l__aTableau_xa_fp},
             \fp_eval:n{\l__aTableau_y_fp+\l__aTableau_col_int*\l__aTableau_ya_fp})
            --(\fp_eval:n{\l__aTableau_x_fp+\l__aTableau_c_int*\l__aTableau_xa_fp},
               \fp_eval:n{\l__aTableau_y_fp+\l__aTableau_c_int*\l__aTableau_ya_fp})
          ;
        }
    }
}

% usage: \__aTableau_tikz_node:nnnn {style} {label} {fp-coordinate name} {entry}
% Draw a TikZ node. This simplifies the code and controls expansion
% TODO: not currently used!
\cs_new_protected:Nn \__aTableau_tikz_node:nnnn
{
    \node[#1] (#2) at (\fp_use:c {l__aTableau_x#3_fp}, \fp_use:c {l__aTableau_y#3_fp}) { \__aTableau_entry:n {#4} };
}

% usage: \__aTableau_put_tikz_node:nnnnn {token list} {style} {label} {fp-coordinate name} {entry}
% Like \__aTableau_put_tikz_node:nVno except that we append the node to the
% token list given by #1
\cs_new_protected:Nn \__aTableau_put_tikz_node:nnnnn
{
    \tl_put_right:cn {l__aTableau_#1_tl} {\node[#2] (#3) at}
    \tl_put_right:ce {l__aTableau_#1_tl} {(\fp_use:c {l__aTableau_x#4_fp}, \fp_use:c {l__aTableau_y#4_fp})}
    \tl_put_right:cn {l__aTableau_#1_tl} { {\__aTableau_entry:n {#5}}; }
}

% usage: \__aTableau_draw_label:
% Add the label to a diagram
\cs_new_protected:Nn \__aTableau_draw_label:
{
   % determine where the label should be attached, which is the (1,1)-box by default
   \fp_set_eq:NN \l__aTableau_xa_fp \l__aTableau_x_fp
   \fp_set_eq:NN \l__aTableau_ya_fp \l__aTableau_y_fp
   \bool_if:nF { \seq_if_empty_p:N \l__aTableau_skew_seq || \l__aTableau_skew_border_bool }
   {   % attach label to the (1,skew_1)-box
       \tl_set:Nn \l_tmpa_tl { 0\seq_item:Nn \l__aTableau_skew_seq {1} }
       \fp_add:Nn \l__aTableau_xa_fp {\l_tmpa_tl*\l__aTableau_box_col_dx_fp}
       \fp_add:Nn \l__aTableau_ya_fp {\l_tmpa_tl*\l__aTableau_box_col_dy_fp}
   }
   % add the label
   \node[aTableau/label] at (\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp) { \__aTableau_entry:n{\l__aTableau_label_tl} };
}

% usage: \__aTableau_draw_cover_boxes:
% Draw the cover boxes using ribbons
\cs_new_protected:Nn \__aTableau_draw_cover_boxes:
{
   \group_begin:
     % override the ribbon style inside this group
     \tikzset{aTableau/ribbonBox/.style=aTableau/coverBox}
     \tikzset{aTableau/ribbon/.style={draw=none,fill=none}}
     \int_zero:N \l__aTableau_r_int
     \seq_map_inline:Nn \l__aTableau_cover_seq
     {
        \int_incr:N \l__aTableau_r_int
        \int_set:No \l__aTableau_row_int { 0\seq_item:NV \l__aTableau_shape_seq \l__aTableau_r_int }
        \int_compare:nNnT {##1} > {\l__aTableau_row_int}
        {
          \tl_clear:N \l_tmpa_tl
          \tl_put_right:Ne \l_tmpa_tl { {\int_use:N \l__aTableau_r_int} }
          \tl_put_right:Ne \l_tmpa_tl { {##1} }
          \tl_put_right:Ne \l_tmpa_tl { \prg_replicate:nn {##1-\l__aTableau_row_int-1} {c} }
          \__aTableau_add_ribbon:nV {ribbon} \l_tmpa_tl
        }
     }
   \group_end:
}

% usage: \__aTableau_draw_skew_boxes:
% Draw the skew boxes using ribbons
\cs_new_protected:Nn \__aTableau_draw_skew_boxes:
{
   \group_begin:
     % override the ribbon style inside this group
     \tikzset{aTableau/ribbonBox/.style=aTableau/skewBox}
     \tikzset{aTableau/ribbon/.style={draw=none,fill=none}}
     \int_zero:N \l__aTableau_r_int
     \seq_map_inline:Nn \l__aTableau_skew_seq
     {
        \int_incr:N \l__aTableau_r_int
        \int_compare:nNnT {##1} > {0}
        {
          \tl_clear:N \l_tmpa_tl
          \tl_put_right:Ne \l_tmpa_tl { {\int_use:N \l__aTableau_r_int} }
          \tl_put_right:Ne \l_tmpa_tl { {##1} }
          \tl_put_right:Ne \l_tmpa_tl { \prg_replicate:nn {##1-1} {c} }
          \__aTableau_add_ribbon:nV {ribbon} \l_tmpa_tl
        }
     }
   \group_end:
}

% usage: \__aTableau_add_row_ends: increment the (xa,ya) and (xb,yb) coordinates down
% one row and add the lines at the left and right hand ends of the row to
% \l__aTableau_border_tl. If this is a tabloid then we only want to add the
% coordinates but otherwise we join them up
\cs_new_protected:Nn \__aTableau_add_row_ends:
{
    \bool_if:NTF \l__aTableau_conjugate_bool
    {
      % adding to the left-hand side
      \fp_add:Nn \l__aTableau_xa_fp {\l__aTableau_box_col_dx_fp}
      \fp_add:Nn \l__aTableau_ya_fp {\l__aTableau_box_col_dy_fp}

      % adding to the right-hand side
      \fp_add:Nn \l__aTableau_xb_fp {\l__aTableau_box_col_dx_fp}
      \fp_add:Nn \l__aTableau_yb_fp {\l__aTableau_box_col_dy_fp}

      \bool_if:NTF \l__aTableau_tabloid_bool
      {
        \tl_put_left:Nx  \l__aTableau_border_tl {--(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)}
        \tl_put_right:Nx \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)--}
      }
      {
        \tl_put_left:Nx  \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--}
        \tl_put_right:Nx \l__aTableau_border_tl {--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)}
      }
    }
    {
      % adding to  the left-hand side
      \fp_add:Nn \l__aTableau_xa_fp {\l__aTableau_box_row_dx_fp}
      \fp_add:Nn \l__aTableau_ya_fp {\l__aTableau_box_row_dy_fp}

      % adding to  the right-hand side
      \fp_add:Nn \l__aTableau_xb_fp {\l__aTableau_box_row_dx_fp}
      \fp_add:Nn \l__aTableau_yb_fp {\l__aTableau_box_row_dy_fp}

      \bool_if:NTF \l__aTableau_tabloid_bool
      {
        \tl_put_left:Nx  \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)}
        \tl_put_right:Nx \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)--}
      }
      {
        \tl_put_left:Nx  \l__aTableau_border_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--}
        \tl_put_right:Nx \l__aTableau_border_tl {--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)}
      }
    }
}

% ---------------------------------------------------------------------------
% Tableaux

% usage: \__aTableau_draw_tableau:n {tableau specifications}
% The entries are first primarily because the \Diagram commands need
% to force the entries to expand
\cs_new_protected:Npn \__aTableau_draw_tableau:n #1
{
    % set the star style
    \tl_set:Nn \l__aTableau_starstyle_tl {aTableau/tableauStar}

    % disable interior boxes if boxes_bool is false
    \bool_if:NF \l__aTableau_boxes_bool { \__aTableau_tikzset_append:nn {tableauBox} {draw=none} }

    % initialise the skew shape for a shifted tableaux
    \bool_if:NT \l__aTableau_shifted_bool
    {
        \seq_clear:N \l__aTableau_skew_seq
        \seq_put_right:No \l__aTableau_skew_seq {0}
    }

    % record the tableau shape as we go so that we can draw the border
    % in finalise_tableau
    \seq_clear:N \l__aTableau_shape_seq

    % the styled boxes are drawn at the end
    \tl_clear:N \l__aTableau_styled_nodes_tl

    % set initial row and column
    \int_zero:N \l__aTableau_row_int
    \int_set:Nn \l__aTableau_col_int { 0\seq_item:Nn \l__aTableau_skew_seq {1}}

    % Parse #1 into the rows and columns with style. Initially we used
    % \seq_set_split:Nnn here, but this required double bracing multi-character
    % entries whenever there was only one entry in a row. Now we peek for the
    % commas and the style simultaneously.
    \__aTableau_peek_tableau:w #1 \q_recursion_tail \q_recursion_stop

    % add the nodes with style
    \l__aTableau_styled_nodes_tl

    % add the labels, ribbons, snobs and border
    \__aTableau_finalise_tableau:
}

% usage: \__aTableau_finalise_tableau:
% As it is used in several places, collect the code that finishes
% drawing the tableau by adding the ribbons, paths, snobs, skew boxes,
% border and the dotted rows and columns.
\cs_new_protected:Nn \__aTableau_finalise_tableau:
{
    % prevent paths and ribbons from updating shape
    \cs_set_eq:NN \__aTableau_update_shape: \prg_do_nothing:

    % add paths
    \tl_if_empty:VF \l__aTableau_paths_tl { \__aTableau_add_ribbon:nV {path} \l__aTableau_paths_tl }

    % add ribbons
    \tl_if_empty:VF \l__aTableau_ribbons_tl { \__aTableau_add_ribbon:nV {ribbon} \l__aTableau_ribbons_tl }

    % paths and ribbons are inside the delimiters, but snobs are not
    \cs_set_eq:NN \__aTableau_update_extrema:n \use_none:n

    % draw border
    \tl_if_blank:VF \l__aTableau_label_tl     { \__aTableau_draw_label: }
    \seq_if_empty:NF \l__aTableau_cover_seq
    {
        \bool_if:NT \l__aTableau_cover_boxes_bool  { \__aTableau_draw_cover_boxes: }
        \bool_if:NT \l__aTableau_cover_border_bool { \__aTableau_draw_border:nn {cover} {coverBorder}  }
    }
    \bool_if:NT \l__aTableau_skew_boxes_bool  { \__aTableau_draw_skew_boxes: }
    \bool_if:NT \l__aTableau_skew_border_bool { \__aTableau_draw_border:nn {skew}  {skewBorder}  }
    \bool_if:NT \l__aTableau_border_bool      { \__aTableau_draw_border:nn {shape} {border} }

    % remove dotted rows and columns
    \seq_if_empty:NF \l__aTableau_dotted_rows_seq \__aTableau_remove_dotted_tableau_rows:
    \seq_if_empty:NF \l__aTableau_dotted_cols_seq \__aTableau_remove_dotted_tableau_cols:

    % add snobs
    \tl_if_empty:VF \l__aTableau_snobs_tl { \__aTableau_add_ribbon:nV {snob} \l__aTableau_snobs_tl }
}

% ---------------------------------------------------------------------------
% box and bead coordinates

% usage: \__aTableau_update_multi_extrema:n {letter}
% Update xmax, ymax and ymin using the x#1_fp and y#1_fp
\cs_new_protected:Npn \__aTableau_update_multi_extrema:n #1
{
    % adjust xmax, ymin and ymax for multishapes
    \fp_set:Nn \l__aTableau_xmax_fp { max(\l__aTableau_xmax_fp, \use:c{l__aTableau_x#1_fp}) }
    \fp_set:Nn \l__aTableau_ymax_fp { max(\l__aTableau_ymax_fp, \use:c{l__aTableau_y#1_fp}) }
    \fp_set:Nn \l__aTableau_ymin_fp { min(\l__aTableau_ymin_fp, \use:c{l__aTableau_y#1_fp}) }
}

\cs_new_protected:Npn \__aTableau_update_xmax:n #1
{
    % adjust xmax for multishapes
    \fp_set:Nn \l__aTableau_xmax_fp { max(\l__aTableau_xmax_fp, \use:c{l__aTableau_x#1_fp}) }
}

% By default, we do not update ymin, ymax and xmax. This only happens for multishapes
\cs_set_eq:NN \__aTableau_update_extrema:n \use_none:n

% usage: \__aTableau_set_box_coordinates_normal:nnn <letter> <row> <col>: given the row and columns
% indices, row and col, define the corresponding coordinates in a tableau
%  - \l__aTableau_xl_fp : x-coordinates
%  - \l__aTableau_yl_fp : y-coordinate
%  - \l__aTableau_name_tl : the node name
% Used by the tableau and diagram commands. Note that row and col both start
% from 0, so the (1,1)-box has row=col=0.
\cs_new_protected:Npn \__aTableau_set_box_coordinates_normal:nnn #1 #2 #3
{
    \tl_set:Nx \l__aTableau_name_tl {\l__aTableau_prefix_tl-\int_eval:n{1+#2}-\int_eval:n {1+#3}}
    \fp_set:cn {l__aTableau_x#1_fp} {\l__aTableau_x_fp+(#2+0.5)*\l__aTableau_box_row_dx_fp+(#3+0.5)*\l__aTableau_box_col_dx_fp }
    \fp_set:cn {l__aTableau_y#1_fp} {\l__aTableau_y_fp+(#2+0.5)*\l__aTableau_box_row_dy_fp+(#3+0.5)*\l__aTableau_box_col_dy_fp }
    \__aTableau_update_extrema:n #1
%   \__aTableau_debug:n {~-~box~coordinates:~\l__aTableau_name_tl~=~(\fp_use:c{l__aTableau_x#1_fp},~\fp_use:c{l__aTableau_y#1_fp})}
}

% usage: \__aTableau_set_box_coordinates_conjugate:nnn <letter> <row> <col>: given the row and columns
% indices, row and col, define the corresponding coordinates in the conjugate tableau
%  - \l__aTableau_xl_fp : x-coordinates
%  - \l__aTableau_yl_fp : y-coordinate
%  - \l__aTableau_name_tl : the node name
% Used by the tableau and diagram commands
\cs_new_protected:Npn \__aTableau_set_box_coordinates_conjugate:nnn #1 #2 #3
{
    \tl_set:Nx \l__aTableau_name_tl {\l__aTableau_prefix_tl-\int_eval:n{1+#3}-\int_eval:n {1+#2}}
    \fp_set:cn {l__aTableau_x#1_fp} {\l__aTableau_x_fp+(#3+0.5)*\l__aTableau_box_row_dx_fp+(#2+0.5)*\l__aTableau_box_col_dx_fp }
    \fp_set:cn {l__aTableau_y#1_fp} {\l__aTableau_y_fp+(#3+0.5)*\l__aTableau_box_row_dy_fp+(#2+0.5)*\l__aTableau_box_col_dy_fp }
    \__aTableau_update_extrema:n #1
%   \__aTableau_debug:n {~-~conjugate~box~coordinates:~\l__aTableau_name_tl~=~(\fp_use:c{l__aTableau_x#1_fp},~\fp_use:c{l__aTableau_y#1_fp})}
}

% by default, normal coordinates are used
\cs_set_eq:NN \__aTableau_set_box_coordinates:nnn \__aTableau_set_box_coordinates_normal:nnn

% usage: \__aTableau_set_bead_coordinates:nnn <letter> <row> <col>: given the row and columns
% indices, row and col, define the corresponding coordinates in a tableau or abacus:
%  - \l__aTableau_xl_fp : x-coordinates
%  - \l__aTableau_yl_fp : y-coordinate
%  - \l__aTableau_name_tl : the node name
% Used by both the tableaux and abacus commands. We shift all of the
% row coordinates up by l__aTableau_abacus_top_fp to ensure the line at the top
% of the abacus is in the expected place.
\cs_new_protected:Npn \__aTableau_set_bead_coordinates:nnn #1 #2 #3
{
    \tl_set:Nx \l__aTableau_name_tl {\l__aTableau_prefix_tl-\fp_to_int:n{#2}-\fp_to_int:n{#3}}
    \fp_set:cn {l__aTableau_x#1_fp} {\l__aTableau_x_fp+((#2+\l__aTableau_abacus_top_fp)*\l__aTableau_abacus_row_dx_fp+(#3)*\l__aTableau_abacus_col_dx_fp)*\l__aTableau_abacus_wd_fp }
    \fp_set:cn {l__aTableau_y#1_fp} {\l__aTableau_y_fp+((#2+\l__aTableau_abacus_top_fp)*\l__aTableau_abacus_row_dy_fp+(#3)*\l__aTableau_abacus_col_dy_fp)*\l__aTableau_abacus_ht_fp }
%   \__aTableau_debug:n {~-~bead~coordinates:~\l__aTableau_name_tl~=~(\fp_use:c{l__aTableau_x#1_fp},~\fp_use:c{l__aTableau_y#1_fp})}
}

% ---------------------------------------------------------------------------
% tableaux boxes/nodes

% By default, tableau boxes are constructed using \__aTableau_box:n = \vbox_to_zero:n
% except when the rotate key is in play when we use \__aTableau_rotated_box:n
% to rotate the contents clockwise through \l__aTableau_rotate_fp degrees so
% that the tableau entries stay vertical
\cs_set_eq:NN \__aTableau_box:n \vbox_to_zero:n
\cs_new_protected:Npn \__aTableau_rotated_box:n #1
{
    \vbox_set_to_ht:Nnn \l_tmpa_box {0pt} {#1}
    \box_rotate:Nn \l_tmpa_box {-\l__aTableau_rotate_fp}
    \box_use_drop:N \l_tmpa_box
}

\cs_new_protected:Npn \__aTableau_valign_bottom:n #1 { \__aTableau_box:n { #1 \vss } }
\cs_new_protected:Npn \__aTableau_valign_centre:n #1 { \__aTableau_box:n { \vss #1 \vss } }
\cs_new_protected:Npn \__aTableau_valign_top:n #1    { \__aTableau_box:n { \vss #1 } }
\cs_set_eq:NN \__aTableau_valign_center:n \__aTableau_valign_centre:n

% We use \vbox_to_zero:n and \hbox_overlap_center:n to ensure that an entry
% does not change the height or width of the node when it is too large.
\cs_new_protected:Npn \__aTableau_entry_math:n #1
{
    \__aTableau_valign:n
    {
        \__aTableau_halign:n
        {
            \tl_if_blank:VF \tikz@textcolor {\color{\tikz@textcolor}}
            \tikz@textfont $~#1 $
        }
    }
}

% and a text version
\cs_new_protected:Npn \__aTableau_entry_text:n #1
{
    \__aTableau_valign:n
    {
        \__aTableau_halign:n
        {
          \tl_if_blank:VF \tikz@textcolor {\color{\tikz@textcolor}}
          \tikz@textfont #1
        }
    }
}

% By default tableau nodes are typeset in math-mode.
\cs_set_eq:NN \__aTableau_entry:n \__aTableau_entry_math:n

% usage: \__aTableau_draw_entry:nn [style] {entry}
% Used by draw_tableau to draw the node entry #2 in the tableau using the style #1
\cs_new_protected:Npn \__aTableau_draw_entry:nn [#1] #2
{
    % exit when we reach the end of the row
    \quark_if_recursion_tail_stop_do:nn {#2}
    {
        % record the column index in the shape for drawing the border
        \seq_put_right:NV \l__aTableau_shape_seq \l__aTableau_col_int
    }

    % compute the node name and its (x,y)-coordinates
    \__aTableau_set_box_coordinates:nVV {l} \l__aTableau_row_int \l__aTableau_col_int

    % when colours is used, set the box fill colour
    \l__aTableau_set_box_fill:nn \l__aTableau_row_int \l__aTableau_col_int

    \tl_if_empty:nTF {#1}
    {   % draw box if it has the default styling
        \node[aTableau/tableauBox] (\l__aTableau_name_tl)
            at (\fp_use:N\l__aTableau_xl_fp, \fp_use:N\l__aTableau_yl_fp)
            {\__aTableau_entry:n{#2}};
    }
    {   % save the node to \l__aTableau_styled_nodes_tl if it is styled
        \__aTableau_put_tikz_node:noVno {styled_nodes} {aTableau/tableauBox,#1} \l__aTableau_name_tl {l} {#2}
    }
    % look for the next entry, or finish
    \int_incr:N \l__aTableau_col_int
    \__aTableau_peek_tableau:w
}

% ---------------------------------------------------------------------------
% diagrams

% usage: \__aTableau_draw_diagram:n {partition}
\cs_new_protected:Npn \__aTableau_draw_diagram:n #1
{
    % convert #1 to the partition \l__aTableau_shape_seq
    \__aTableau_set_partition:nn {shape} {#1}

    % set the skew shape for shifted tableaux
    \bool_if:NT \l__aTableau_shifted_bool
    {
        \seq_clear:N \l__aTableau_skew_seq
        \seq_map_inline:Nn \l__aTableau_shape_seq
        {
            \seq_put_right:No \l__aTableau_skew_seq {\int_use:N \l__aTableau_row_int}
            \int_incr:N \l__aTableau_row_int
        }
        \int_zero:N \l__aTableau_row_int
    }

    % depending on \l__aTableau_show_tl, generate the tableau entries
    \str_case:VnF \l__aTableau_show_tl
    {
        {contents} { \__aTableau_shape_to_content:N  \l__aTableau_shape_seq }
        {last}     { \__aTableau_shape_to_last:N     \l__aTableau_shape_seq }
        {hooks}    { \__aTableau_shape_to_hook:N     \l__aTableau_shape_seq }
        {first}    { \__aTableau_shape_to_first:N    \l__aTableau_shape_seq }
        {residues} { \__aTableau_shape_to_residue:N  \l__aTableau_shape_seq }
        {}         { \__aTableau_diagram_for_shape:N \l__aTableau_shape_seq }
    }
    {
        \msg_error:nnx {aTableau} {unrecognised-entries} {\l__aTableau_show_tl}
    }
    \__aTableau_draw_tableau:V \l__aTableau_entries_tl
}

% ---------------------------------------------------------------------------
% multitableau and their diagrams

% usage: \__aTableau_draw_multishape:n diagram|tableau
% Draw a multitableau or multidiagram. Most of the work is in calculating the
% x-coordinates of the origins and each diagram, their separators and the
% maximal y-coordinates, for drawing the delimiters. We also need to set
% various keys for the components, so that they work correctly.
\cs_new_protected:Npn \__aTableau_draw_multishape:n #1
{
    % save the prefix name so that we can modify it
    \tl_set_eq:NN \l__aTableau_multiprefix_tl \l__aTableau_prefix_tl

    % check for conjugation
    \bool_if:NT \l__aTableau_conjugate_bool { \seq_reverse:N \l__aTableau_component_seq }

    % reset the variables that we need
    \int_zero:N \l__aTableau_component_int % component index

    % We will increment x_fp to give the origins of the component diagrams.
    % For now we record the position of the x-coordinates of the left brace,
    % which will be placed after the diagrams have been drawn we first have to
    % determine their height.
    \seq_clear:N \l__aTableau_xsep_seq
    \seq_put_right:Nx \l__aTableau_xsep_seq {\fp_to_decimal:N\l__aTableau_x_fp}

    % keep track of min/max y-coordinates used and max x-coordinate
    \fp_set_eq:NN \l__aTableau_xmax_fp \l__aTableau_x_fp
    \fp_set:Nn \l__aTableau_ymax_fp {\l__aTableau_y_fp + \l__aTableau_box_row_dy_fp/2} % middle of box
    \fp_set_eq:NN \l__aTableau_ymin_fp \l__aTableau_ymax_fp

    \seq_map_inline:Nn \l__aTableau_component_seq
    {
        % increment component and set prefix, charge, skew, ribbons and snobs
        \int_incr:N \l__aTableau_component_int

        % update ymin, ymax and xmax (re-enable each time as this is disabled when placing ribbons)
        \fp_compare:nNnTF {\l__aTableau_rows_fp} = {0}
        { \cs_set_eq:NN \__aTableau_update_extrema:n \__aTableau_update_multi_extrema:n } % update xmax, ymin, ymax
        { \cs_set_eq:NN \__aTableau_update_extrema:n \__aTableau_update_xmax:n }  % update only xmax

        % change the node prefix to include the component index
        \tl_set:No \l__aTableau_prefix_tl {\l__aTableau_multiprefix_tl-\int_use:N\l__aTableau_component_int}

        % charge defaults to zero if not set
        \int_set:Nn \l__aTableau_charge_int {0\seq_item:NV \l__aTableau_charge_seq \l__aTableau_component_int}

        % set the multi-component sequence keys from the corresponding multi sequence
        \clist_map_inline:nn {dotted_rows, dotted_cols}
        {
          % if multi####1 is empty then clear the ####1 sequence, otherwise set it equal to component value
          \seq_if_empty:cTF {l__aTableau_multi####1_seq}
          { \seq_clear:c {l__aTableau_####1_seq} }
          {
              \tl_set:Nx \l_tmpb_tl {\seq_item:cV {l__aTableau_multi####1_seq} \l__aTableau_component_int}
              \seq_set_from_clist:co {l__aTableau_####1_seq} {\l_tmpb_tl}
          }
        }

        % set the multi-component tl-keys from the corresponding multi sequence
        \clist_map_inline:nn {label, paths, ribbons, snobs}
        {
          % if multi####1 is empty then clear the ####1 sequence, otherwise set it equal to component value
          \seq_if_empty:cTF {l__aTableau_multi####1_seq}
          { \seq_clear:c {l__aTableau_####1_seq} }
          {
              \tl_set:Nx \l_tmpb_tl {\seq_item:cV {l__aTableau_multi####1_seq} \l__aTableau_component_int}
              \tl_set:cV {l__aTableau_####1_tl} \l_tmpb_tl
          }
        }

        % cover and skew are handled separately because they use set_partition
        \clist_map_inline:nn {cover, skew}
        {
            \seq_if_empty:cTF {l__aTableau_multi####1_seq}
            { \seq_clear:c {l__aTableau_####1_seq} }
            {
                \tl_set:Nx \l_tmpb_tl {\seq_item:cV {l__aTableau_multi####1_seq} \l__aTableau_component_int}
                \__aTableau_set_partition:nV {####1} \l_tmpb_tl
            }
        }

        % determine the coordinates for the diagram/tableau
        %  - \l__aTableau_c_int: number of columns in first row
        %  - \l__aTableau_r_int: number of rows in components
        \bool_if:nTF { \str_if_empty_p:n {##1} || \str_if_eq_p:nn {##1} {...} }
        {
          \int_set:Nn \l__aTableau_c_int {1}
          \int_set:Nn \l__aTableau_r_int {1}
        }
        {
            % the component is nonempty
            \tl_if_eq:nnTF {#1} {diagram}
            {
              \__aTableau_set_partition:nn {shape} {##1}
              \int_set:No \l__aTableau_c_int {\seq_item:Nn \l__aTableau_shape_seq {1}+0\seq_item:Nn \l__aTableau_skew_seq {1}}

              % set r_int equal to the number of nonzero rows in shape_seq
              \int_set:No \l__aTableau_r_int { \seq_count:N \l__aTableau_shape_seq }
            }
            { % coordinates for tableaux
              \seq_set_from_clist:Nn \l_tmpa_seq {##1}
              \int_set:No \l__aTableau_c_int {0\seq_item:Nn \l__aTableau_skew_seq {1}} % initialise to skew length
              \tl_set:Nx \l_tmpc_tl {\seq_item:Nn \l_tmpa_seq {1}} % first row of tableau
              \__aTableau_count_row:x \l_tmpc_tl % length of first row + skew

              % set r_int equal to the number of nonzero rows in shape_seq
              \int_set:No \l__aTableau_r_int { \seq_count:N \l_tmpa_seq }
            }
        }

        % now that we have the coordinates we need, we compute the
        % x-coordinates of the diagram origin and the separators

        % need to switch for conjugate partitions
        \bool_if:NTF \l__aTableau_conjugate_bool
        { % conjugating
            % the origin is c * col_dx units from the separator + xoffset
            \fp_add:Nn \l__aTableau_x_fp
            {
                abs(\l__aTableau_c_int*\l__aTableau_box_row_dx_fp) % number of columns
                + 0\seq_item:NV \l__aTableau_xoffsets_seq \l__aTableau_component_int  % x-offset
            }
            % the next separator is is r * row_dx units from the origin
            \fp_set:Nn \l__aTableau_xsep_fp
            {
                abs(\l__aTableau_r_int*\l__aTableau_box_col_dx_fp)
                + \l__aTableau_separation_fp
            }

            % compute maximum height of the diagram
            \fp_set:Nn \l__aTableau_yb_fp
            {
                \l__aTableau_c_int*\l__aTableau_box_row_dy_fp
                  + \l__aTableau_r_int*abs(\l__aTableau_box_col_dy_fp)
                  + 0\seq_item:NV \l__aTableau_yoffsets_seq \l__aTableau_component_int
            }
        }
        {   % not conjugating
            % the origin is r * row_dx units from the separator + xoffset
            \fp_add:Nn \l__aTableau_x_fp
            {
                abs(\l__aTableau_r_int*\l__aTableau_box_row_dx_fp) % number of columns
                + 0\seq_item:NV \l__aTableau_xoffsets_seq \l__aTableau_component_int  % x-offset
            }
            % the next separator is is c * col_dx units from the origin
            \fp_set:Nn \l__aTableau_xsep_fp
            {
                abs(\l__aTableau_c_int*\l__aTableau_box_col_dx_fp) % number of columns
                + \l__aTableau_separation_fp
            }
            % compute maximum height of the diagram
            \fp_set:Nn \l__aTableau_yb_fp
            {
                \l__aTableau_r_int*\l__aTableau_box_row_dy_fp
                  + \l__aTableau_c_int*\l__aTableau_box_col_dy_fp
                  + 0\seq_item:NV \l__aTableau_yoffsets_seq \l__aTableau_component_int
             }
        }

        % Having determined the positions of the diagram origins and separators, we are ready to draw the diagram
        % First set the y-coordinate for the origin of the current component
        \fp_add:Nn \l__aTableau_y_fp {0\seq_item:NV \l__aTableau_yoffsets_seq \l__aTableau_component_int}

        % special processing for empty diagrams and ...
        \str_case:nnF {##1}
        {
            {}
              {
                 % an empty diagram -> \l__aTableau_empty_tl
                 \__aTableau_set_box_coordinates:nnn {a} {0} {0}
                 \node[aTableau/separatorSymbol] at (\fp_use:N \l__aTableau_xa_fp, \fp_use:N \l__aTableau_ya_fp){ \__aTableau_entry:n {\l__aTableau_empty_tl} };
              }
            {...}
              {
                 % insert dots. ?? Replace \cdots with \l__aTableau_dots_tl ??
                 \__aTableau_set_box_coordinates:nnn {a} {0} {0}
                 \node[aTableau/separatorSymbol] at (\fp_use:N \l__aTableau_xa_fp, \fp_use:N \l__aTableau_ya_fp){ \__aTableau_entry:n{\cdots} };
              }
        }
        {
            % draw the diagram/tableau
            \use:c {__aTableau_draw_#1:n} {##1}
        }

        % increment the origin by the separation distance and record the
        % x-coordinate of the separator
        \fp_set:Nn \l__aTableau_x_fp { \l__aTableau_xmax_fp+\l__aTableau_box_wd_fp/2+\l__aTableau_separation_fp }
        \seq_put_right:Nx \l__aTableau_xsep_seq {\fp_to_decimal:N\l__aTableau_x_fp}

        % add the separation distance to x_fp for the next component
        \fp_add:NV \l__aTableau_x_fp \l__aTableau_separation_fp % separation

    }  % end of seq_map_inline to draw component diagrams/tableau

    % All of the component diagrams/tableaux have been drawn
    % It remains to add the separators. First we adjust ymin and ymax
    \bool_if:NT \l__aTableau_separators_bool
    {

      % when rows_fp is nonzero it sets the maximum y-coordinate, otherwise we
      % need to adjust ymin and ymax by half the box height
      \fp_compare:nNnTF {\l__aTableau_rows_fp} > {0}
      {
        \fp_compare:nNnTF {\l__aTableau_box_row_dy_fp} > {0}
        {
          \fp_add:Nn \l__aTableau_ymax_fp {  (\l__aTableau_rows_fp-0.5)*\l__aTableau_box_ht_fp }
          \fp_add:Nn \l__aTableau_ymin_fp {  -0.5*\l__aTableau_box_ht_fp }
        }
        {
          \fp_add:Nn \l__aTableau_ymin_fp {  (0.5-\l__aTableau_rows_fp)*\l__aTableau_box_ht_fp }
          \fp_add:Nn \l__aTableau_ymax_fp {  0.5*\l__aTableau_box_ht_fp }
        }
      }
      {
        % adjust ymin and ymax count as they count from the centre of the box
        \fp_add:Nn \l__aTableau_ymax_fp { \l__aTableau_box_ht_fp/2}
        \fp_add:Nn \l__aTableau_ymin_fp {-\l__aTableau_box_ht_fp/2}
      }

      \fp_set:Nn \l__aTableau_y_fp    {(\l__aTableau_ymin_fp+\l__aTableau_ymax_fp)/2} % midway between ymin and ymax
      \fp_set:Nn \l__aTableau_ymax_fp { \l__aTableau_ymax_fp-\l__aTableau_ymin_fp }   % maximum height

      % add left delimiter: need to use \path to set the colour
      \seq_pop_left:NN \l__aTableau_xsep_seq \l_tmpa_tl
      \tl_if_blank:VF \l__aTableau_left_delimiter_tl
      {
        \path[aTableau/delimiterPath] (\fp_eval:n{\l_tmpa_tl-\l__aTableau_separation_fp/2}, \fp_use:N \l__aTableau_y_fp) node[aTableau/leftDelimiter] {};
      }

      % add right delimiter
      \seq_pop_right:NN \l__aTableau_xsep_seq \l_tmpa_tl
      \tl_if_blank:VF \l__aTableau_right_delimiter_tl
      {
        \path[aTableau/delimiterPath] (\fp_eval:n{\l_tmpa_tl-\l__aTableau_separation_fp/2}, \fp_use:N \l__aTableau_y_fp) node[aTableau/rightDelimiter] {};
      }

      % the internal separators
      \tl_set:Ne \l_tmpa_tl {\fp_to_decimal:N \l__aTableau_ymin_fp }                        % ymin
      \tl_set:Ne \l_tmpb_tl {\fp_to_decimal:n {\l__aTableau_y_fp + \l__aTableau_ymax_fp/2}} % ymax
      \seq_map_inline:Nn \l__aTableau_xsep_seq
      {
        % add the separator
        \str_case:VnF \l__aTableau_separator_tl
        {
            {|} { \draw[aTableau/separatorLine](##1,\l_tmpa_tl)--(##1,\l_tmpb_tl); }
        }
        {   % any other separator is assumed to be text
            \node[aTableau/separatorSymbol] at (##1,\fp_use:N \l__aTableau_y_fp){\l__aTableau_separator_tl};
        }
      }
    }
}

% usage: \__aTableau_multidiagram:n {entries}
% The entries are first primarily because the \Diagram commands needs
% to force the entries to expand
\cs_new_protected:Npn \__aTableau_multidiagram:n #1
{
    % separate the component partitions
    \seq_set_split:Nnn \l__aTableau_component_seq {|} {#1}

    % when entries=last, we need to set the charge
    \tl_if_eq:NnT \l__aTableau_show_tl {last}
    {
        \seq_clear:N \l__aTableau_charge_seq
        \int_zero:N \l__aTableau_c_int % cumulative total of component sizes
        \seq_set_eq:NN \l_tmpc_seq \l__aTableau_component_seq
        \seq_reverse:N \l_tmpc_seq
        \seq_map_inline:Nn \l_tmpc_seq
        {
            \seq_put_left:No \l__aTableau_charge_seq {\int_use:N \l__aTableau_c_int}
            \__aTableau_set_partition:nn {shape} {##1}
            \seq_map_inline:Nn \l__aTableau_shape_seq { \int_add:Nn \l__aTableau_c_int {####1} }
        }
    }

    % determine the coordinates of the components of the diagram
    \__aTableau_draw_multishape:n {diagram}
}

% usage: \__aTableau_multitableau:n {entries}
% The entries are first primarily because the \Diagram commands need
% to force the entries to expand
\cs_new_protected:Npn \__aTableau_multitableau:n #1
{
    % separate the entries of the component tableaux
    \seq_set_split:Nnn \l__aTableau_component_seq {|} {#1}

    % determine the coordinates of the components of the tableau
    \__aTableau_draw_multishape:n {tableau}
}

% ---------------------------------------------------------------------------
% Ribbon tableaux

% usage: \__aTableau_ribbon_tableau:n {ribbons}
% Draw a ribbon tableau. The ribbons are specified by
%   (ribbon style) ij sequences of r's and c's with optional style and
%   with text as a subscript. Here i and j are the row and column
%   indices of the head of the ribbon
\cs_new_protected:Npn \__aTableau_ribbon_tableau:n #1
{
    % set the star style
    \tl_set:Nn \l__aTableau_starstyle_tl {aTableau/tableauStar}

    \seq_if_empty:NTF \l__aTableau_shape_seq
    {
        % record the shape as we draw the border
        \cs_set_eq:NN \__aTableau_update_shape: \__aTableau_update_ribbon_tableau_shape:
    }
    {
        % shape already set
        \cs_set_eq:NN \__aTableau_update_shape: \prg_do_nothing:
    }

    % draw the ribbon tableau by drawing each of the ribbons
    \tl_set:Nn \l__aTableau_ribbon_type_tl {ribbon} % change ribbon type to ribbon
    \__aTableau_peek_ribbon:w #1 \q_recursion_tail \q_recursion_stop

    % draw the tableau border, adding associated bells and whistles
    \__aTableau_finalise_tableau:
}

% usage: \__aTableau_add_ribbon:n {ribbon} add a ribbon to the tableau
% The code for adding ribbons is slightly different depending on on whether
% \l__aTableau_ribbbon_sty[e is equal to 'ribbon' or 'path'
\cs_new_protected:Npn \__aTableau_add_ribbon:nn #1 #2
{
    \tl_set:Nn \l__aTableau_ribbon_type_tl {#1}   % change ribbon type to ribbon
    \__aTableau_peek_ribbon:w #2 \q_recursion_tail \q_recursion_stop
}

% We peek for the commas in the ribbon specifications so that it is not
% necessary to enclose "complicated" styles inside both square brackets and
% braces using [{...}].
\cs_new_protected:Npn \__aTableau_peek_ribbon:w
{
    \peek_remove_spaces:n { % ignore spaces
        \peek_charcode_remove:NTF ,
        {
            % look for the next ribbon
            \__aTableau_peek_ribbon:w
         }
         {
            % reset the sequences that store the ribbon specifications
            \seq_clear:N \l__aTableau_texts_seq  % will contain node text
            \seq_clear:N \l__aTableau_styles_seq % will contain node styles
            \seq_clear:N \l__aTableau_rcs_seq    % will contain node (row,col)-indices
            \__aTableau_peek_ribbon_style:w
         }
    }
}

% usage: \__aTableau_peek_ribbon_style:w {ribbon specifications}
% look for (ribbon) style inside parentheses: (style)
\cs_new_protected:Npn \__aTableau_peek_ribbon_style:w
{
    \peek_remove_spaces:n
    { % ignore spaces
      \peek_charcode:NTF (
        { \__aTableau_save_ribbon_style:n   }
        { \__aTableau_save_ribbon_style:n ()}
    }
}

% usage: \__aTableau_save_ribbon_style:n {style}
% read the style (style) and save in \l__aTableau_ribbon_style_tl
% and then peek for [style]rc...
\cs_new_protected:Npn \__aTableau_save_ribbon_style:n (#1)
{
    \tl_set:Nn \l__aTableau_ribbon_style_tl {#1}
    \__aTableau_peek_style:nw {save_ribbon_head:nnn}
}

% usage: \__aTableau_initialise_path_head:
% Start \l__aTableau_ribbon_path_tl for a path
\cs_new_protected:Nn \__aTableau_initialise_path_head:
{   % adding a ribbon path
    \tl_set:Nx \l__aTableau_ribbon_path_tl
    {
        (\fp_use:N\l__aTableau_xl_fp,\fp_use:N\l__aTableau_yl_fp)
            node[aTableau/pathBox,\l__aTableau_ribbon_style_tl]{\__aTableau_entry:n{\l__aTableau_path_box_tl}}
    }
}

% usage: \__aTableau_set_ribbon_coordinates:nnnn #1 #2 #3 #4
% Define the four ribbon coordinates:
%   - \l__aTableau_ribbon_a_tl = (r,c)                    c----b
%   - \l__aTableau_ribbon_b_tl = (r-1,c)                  |    |
%   - \l__aTableau_ribbon_c_tl = (r-1,c-1)                |    |
%   - \l__aTableau_ribbon_d_tl = (r,c-1)                  d----a
\cs_new_protected:Nn \__aTableau_set_ribbon_coordinates:
{
    \tl_set:Ne \l__aTableau_ribbon_a_tl {\int_use:N\l__aTableau_row_int|\int_use:N\l__aTableau_col_int}
    \tl_set:Ne \l__aTableau_ribbon_b_tl {\int_eval:n{\l__aTableau_row_int-1}|\int_use:N\l__aTableau_col_int}
    \tl_set:Ne \l__aTableau_ribbon_c_tl {\int_eval:n{\l__aTableau_row_int-1}|\int_eval:n{\l__aTableau_col_int-1}}
    \tl_set:Ne \l__aTableau_ribbon_d_tl {\int_use:N\l__aTableau_row_int|\int_eval:n{\l__aTableau_col_int-1}}
}

% usage: \__aTableau_add_edges_to_ribbon:
% The edges on the boundary of the ribbon are stored in \l__aTableau_ribbon_prop,
% where the edge is the key and the values is empty. We use a property list
% because this is the only data structure that allows us to easily add and
% remove entries. The edge joining (r1,c1) and (r1,c2) is stored as
%     r1|c1|r2|c2 if (r1,c1) > (r2,c2)
%     r2|c2|r1|c1 if (r2,c2) > (r1,c1)
% As the vertices are ordered, we do not need to check to see if there is an
% edge from X to Y, or an edge from Y to X.
\cs_new_protected:Nn \__aTableau_add_edges_to_ribbon:
{
    % add edge a->b
    \tl_set:Ne \l_tmpa_tl {\l__aTableau_ribbon_a_tl|\l__aTableau_ribbon_b_tl}
    \prop_get:NVNTF \l__aTableau_ribbon_prop \l_tmpa_tl \l_tmpv_tl
      { \prop_remove:NV \l__aTableau_ribbon_prop \l_tmpa_tl }
      { \prop_put:NVn \l__aTableau_ribbon_prop \l_tmpa_tl {} }
    % add edge b->c
    \tl_set:Ne \l_tmpa_tl {\l__aTableau_ribbon_b_tl|\l__aTableau_ribbon_c_tl}
    \prop_get:NVNTF \l__aTableau_ribbon_prop \l_tmpa_tl \l_tmpv_tl
      { \prop_remove:NV \l__aTableau_ribbon_prop \l_tmpa_tl }
      { \prop_put:NVn \l__aTableau_ribbon_prop \l_tmpa_tl {} }
    % add edge d->c
    \tl_set:Ne \l_tmpa_tl {\l__aTableau_ribbon_d_tl|\l__aTableau_ribbon_c_tl}
    \prop_get:NVNTF \l__aTableau_ribbon_prop \l_tmpa_tl \l_tmpv_tl
      { \prop_remove:NV \l__aTableau_ribbon_prop \l_tmpa_tl }
      { \prop_put:NVn \l__aTableau_ribbon_prop \l_tmpa_tl {} }
    % add edge a->d
    \tl_set:Ne \l_tmpa_tl {\l__aTableau_ribbon_a_tl|\l__aTableau_ribbon_d_tl}
    \prop_get:NVNTF \l__aTableau_ribbon_prop \l_tmpa_tl \l_tmpv_tl
      { \prop_remove:NV \l__aTableau_ribbon_prop \l_tmpa_tl }
      { \prop_put:NVn \l__aTableau_ribbon_prop \l_tmpa_tl {} }
}

% usage: \__aTableau_initialise_ribbon_head:
% Initialise a ribbon by adding the first four edges to the
% \l__aTableau_ribbon_prop
\cs_new_protected:Nn \__aTableau_initialise_ribbon_head:
{
    \prop_clear:N \l__aTableau_ribbon_prop
    \__aTableau_set_ribbon_coordinates:
    \__aTableau_add_edges_to_ribbon:
}

% usage: \__aTableau_save_ribbon_head:nnn [style] {row index} {col index}
% Save the style, row and column indices for the head of the ribbon
% and then compute the coordinates of the three "external nodes" in the
% head.
\cs_new_protected:Npn \__aTableau_save_ribbon_head:nnn [#1] #2 #3
{
    % check for syntax errors to prevent an endless loop
    \quark_if_recursion_tail_stop_do:nn {#2} { \msg_error:nnn {aTableau} {invalid-ribbon-head} {no~x-coordinate~given~#1,#2,#3.} }
    \quark_if_recursion_tail_stop_do:nn {#3} { \msg_error:nnn {aTableau} {invalid-ribbon-head} {no~y-coordinate~given~#1,#2,#3.} }

    % save any style
    \seq_put_right:Nx \l__aTableau_styles_seq {#1}

    % record the row and column indices of the head
    \int_set:No \l__aTableau_row_int { \int_eval:n {#2-1} }
    \int_set:No \l__aTableau_col_int { \int_eval:n {#3-1} }
    \seq_put_right:NV \l__aTableau_rcs_seq \l__aTableau_row_int
    \seq_put_right:NV \l__aTableau_rcs_seq \l__aTableau_col_int

    % update the shape to include the head node (#3,#4)
    \__aTableau_update_shape:

    % make (xl,yl) point to the initial box coordinates (is this necessary??)
    \__aTableau_set_box_coordinates:nVV {l} \l__aTableau_row_int \l__aTableau_col_int

    % initialise the start of the path/ribbon
    \use:c {__aTableau_initialise_ \l__aTableau_ribbon_type_tl _head:}

    \__aTableau_peek_ribbon_comma:nnw {peek_ribbon_text:w} {}
}

% usage: \__aTableau_peek_ribbon_text:w
% peek for subscripted text _{text} in the ribbon
\cs_new_protected:Npn \__aTableau_peek_ribbon_text:w
{
    \peek_remove_spaces:n
    { % ignore spaces
      \peek_charcode_remove:NTF _
        { \__aTableau_save_ribbon_text:n } % set the text of this box
        {
            % set the text of this box to empty
            \seq_put_right:Nn \l__aTableau_texts_seq {}
            % look for next entry in the ribbon
            \__aTableau_peek_ribbon_comma:nnw {peek_style:nw}  {save_ribbon:nn}
        }
    }
}


% usage: \__aTableau_peek_ribbon_comma:nnw {next function} {parameters}
\cs_new_protected:Npn \__aTableau_peek_ribbon_comma:nnw #1 #2
{
    \peek_remove_spaces:n
    { % ignore spaces
        \peek_charcode_remove:NTF ,
        {
            % if there is no _ indicating following text, then we might see a
            % comma in which case we need to start a new ribbon
            \__aTableau_draw_ribbon:
            \__aTableau_peek_ribbon:w
        }
        {
            \tl_if_empty:nTF {#2}
            { \use:c {__aTableau_#1}      }
            { \use:c {__aTableau_#1} {#2} }
        }
    }
}


% usage: \__aTableau_save_ribbon_text:n {text}
% save any text for a rode in the ribbon in \l__aTableau_texts_seq
\cs_new_protected:Npn \__aTableau_save_ribbon_text:n #1
{
    \seq_put_right:No \l__aTableau_texts_seq {#1}
    \__aTableau_peek_ribbon_comma:nnw {peek_style:nw}  {save_ribbon:nn}
}

% usage: \__aTableau_extend_ribbon_c:
% adjust the edges in \l__aTableau_ribbon_prop by pushing through the c-edge
\cs_new_protected:Nn \__aTableau_extend_ribbon_c:
{
    \int_decr:N \l__aTableau_col_int  % c -> c-1
    \__aTableau_set_ribbon_coordinates:
    \prop_get:NeNT \l__aTableau_ribbon_prop {\l__aTableau_ribbon_a_tl|\l__aTableau_ribbon_b_tl} \l_tmpc_tl
    {
        \__aTableau_add_edges_to_ribbon:
    }
}

% usage: \__aTableau_extend_ribbon_C:
% adjust the edges in \l__aTableau_ribbon_prop by pushing through the C-edge
\cs_new_protected:Nn \__aTableau_extend_ribbon_C:
{
    \int_incr:N \l__aTableau_col_int  % c -> c+1
    \__aTableau_set_ribbon_coordinates:
    \prop_get:NeNT \l__aTableau_ribbon_prop {\l__aTableau_ribbon_d_tl|\l__aTableau_ribbon_c_tl} \l_tmpc_tl
    {
        \__aTableau_add_edges_to_ribbon:
    }
}

% usage: \__aTableau_extend_ribbon_R:
% adjust the edges in \l__aTableau_ribbon_prop by pushing through the R-edge
\cs_new_protected:Nn \__aTableau_extend_ribbon_R:
{
    \int_decr:N \l__aTableau_row_int  % r -> r-1
    \__aTableau_set_ribbon_coordinates:
    \prop_get:NeNT \l__aTableau_ribbon_prop {\l__aTableau_ribbon_a_tl|\l__aTableau_ribbon_d_tl} \l_tmpc_tl
    {
        \__aTableau_add_edges_to_ribbon:
    }
}

% usage: \__aTableau_extend_ribbon_r:
% adjust the edges in \l__aTableau_ribbon_prop by pushing through the r-edge
\cs_new_protected:Nn \__aTableau_extend_ribbon_r:
{
    \int_incr:N \l__aTableau_row_int  % r -> r+1
    \__aTableau_set_ribbon_coordinates:
    \prop_get:NeNT \l__aTableau_ribbon_prop {\l__aTableau_ribbon_b_tl|\l__aTableau_ribbon_c_tl} \l_tmpc_tl
    {
        \__aTableau_add_edges_to_ribbon:
    }
}

% usage: \__aTableau_extend_ribbon:n {direction=}
% Extend a ribbon path. Here #1 is either c, C, r or R. We extend the edges of
% the ribbon in \l__aTableau_ribbon_prop using the four commands above.
\cs_new_protected:Npn \__aTableau_extend_ribbon:n #1
{
    \cs_if_exist_use:cF { __aTableau_extend_ribbon_ #1: }
    {   % error for invalid ribbon specification
        \msg_error:nnn {aTableau} { invalid-ribbon-specification } {#1 }
    }
}

% usage: \__aTableau_extend_path_c:
\cs_new_protected:Nn \__aTableau_extend_path_c:
{
    \int_decr:N \l__aTableau_col_int
    \fp_sub:Nn \l__aTableau_xl_fp {\l__aTableau_box_col_dx_fp}
    \fp_sub:Nn \l__aTableau_yl_fp {\l__aTableau_box_col_dy_fp}
}

% usage: \__aTableau_extend_path_C:
\cs_new_protected:Nn \__aTableau_extend_path_C:
{
    \int_incr:N \l__aTableau_col_int
    \fp_add:Nn \l__aTableau_xl_fp {\l__aTableau_box_col_dx_fp}
    \fp_add:Nn \l__aTableau_yl_fp {\l__aTableau_box_col_dy_fp}
}

% usage: \__aTableau_extend_path_r:
\cs_new_protected:Nn \__aTableau_extend_path_r:
{
    \int_incr:N \l__aTableau_row_int
    \fp_add:Nn \l__aTableau_xl_fp {\l__aTableau_box_row_dx_fp}
    \fp_add:Nn \l__aTableau_yl_fp {\l__aTableau_box_row_dy_fp}
}

% usage: \__aTableau_extend_path_R:
\cs_new_protected:Nn \__aTableau_extend_path_R:
{
    \int_decr:N \l__aTableau_row_int
    \fp_sub:Nn \l__aTableau_xl_fp {\l__aTableau_box_row_dx_fp}
    \fp_sub:Nn \l__aTableau_yl_fp {\l__aTableau_box_row_dy_fp}
}

% usage: \__aTableau_add_to_path:n
% Extend a ribbon path. Here #1 is either r or c
\cs_new_protected:Npn \__aTableau_extend_path:n #1
{
    \cs_if_exist_use:cTF { __aTableau_extend_path_ #1: }
    {
        \tl_put_right:Nx \l__aTableau_ribbon_path_tl
        {
            --(\fp_use:N\l__aTableau_xl_fp,\fp_use:N\l__aTableau_yl_fp)
              node[aTableau/pathBox,\l__aTableau_ribbon_style_tl]{\__aTableau_entry:n{\l__aTableau_path_box_tl}}
        }
    }
    {
       \msg_error:nnx {aTableau} {invalid-ribbon-specification} {#1}
    }
}

% For each successive r and c in the ribbon specification, determine the
% surrounding coordinates in the ribbon and save any custom styles in
% \l__aTableau_styles_seq and then repeat
\cs_new_protected:Npn \__aTableau_save_ribbon:nn [#1] #2
{
    % draw the ribbon when we run out of nodes
    \quark_if_recursion_tail_stop_do:nn {#2} { \__aTableau_draw_ribbon: }

    % Add the new coordinate(s) to \l__aTableau_ribbon_path_tl. This is
    % different for ribbons and paths
    \use:c {__aTableau_extend_ \l__aTableau_ribbon_type_tl :n} {#2}

    % update the shape to include the new node
    \__aTableau_update_shape:

    % save the style and row and column indices
    \seq_put_right:No \l__aTableau_styles_seq {#1} % record the style of the head
    \seq_put_right:NV \l__aTableau_rcs_seq \l__aTableau_row_int % record the row of the node
    \seq_put_right:NV \l__aTableau_rcs_seq \l__aTableau_col_int % record the column of the node

    % TODO: rather than saving the data here, we should draw the ribbons
    % now -- or, rather, save this data as TikZ commands in
    % \l__aTableau_styled_nodes_tl and l__aTableau_unstyled_nodes_tl so
    % that the data can be printed as soon as the ribbon is read. This
    % should be faster because it will remove the need to store the
    % ribbon data in the \l__aTableau_capture_*_seq sequences and then
    % retrieve this information later in \__aTableau_draw_ribbon:

    % check to see if this node has any text
    \__aTableau_peek_ribbon_comma:nnw {peek_ribbon_text:w} {}
}

% update the shape of the ribbon tableau using the current values of
% \l__aTableau_row_int and \l__aTableau_col_int
\cs_new_protected:Nn \__aTableau_update_ribbon_tableau_shape:
{
    % ensure that \l__aTableau_shape_seq has at least a 0 in each row
    \int_step_inline:nn { \l__aTableau_row_int+1 - \seq_count:N \l__aTableau_shape_seq }
    {
      \seq_put_right:Nn \l__aTableau_shape_seq {0}
    }
    % for shifted tableaux we also need to ensure that skew is big enough
    \bool_if:NT \l__aTableau_shifted_bool
    {
      \int_set:Nn \l_tmpa_int {\seq_count:N \l__aTableau_skew_seq}
      \int_step_inline:nnn {0}  { \l__aTableau_row_int - \l_tmpa_int }
      {
        \seq_put_right:Nx \l__aTableau_skew_seq {\int_eval:n{\l_tmpa_int+##1 }}
      }
    }
    \int_compare:nNnT {0\seq_item:Nn \l__aTableau_shape_seq {\l__aTableau_row_int+1}} < {\l__aTableau_col_int+1}
    {
      \seq_set_item:Nox \l__aTableau_shape_seq {\l__aTableau_row_int+1} { \int_eval:n{\l__aTableau_col_int+1} }
    }
}

% usage: \__aTableau_start_ribbon_path:nnnn r1|c1|r2|c2
% Start a (new connected component of) the path around the boundary of a ribbon
% by adding the edge (r1,c1)--(r2,c2) to \l__aTableau_ribbon_path_tl. Set
% \l_tmpc_tl to (r2,c2) and set \l__aTableau_adjacent_edges_clist equal to the
% list of possible edges in the ribbon boundary that are connected to (r2,c2).
% We then search
\cs_new_protected:Npn \__aTableau_start_ribbon_path:nnnn #1|#2|#3|#4|
{
    \__aTableau_set_box_coordinates:nnn {a} {#1} {#2}
    \__aTableau_set_box_coordinates:nnn {b} {#3} {#4}
    \tl_put_right:Ne \l__aTableau_ribbon_path_tl {(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp)}
    \clist_set:Ne \l__aTableau_adjacent_edges_clist { #3|#4|\int_eval:n{#3-1}|#4, \int_eval:n{#3+1}|#4|#3|#4, #3|#4|#3|\int_eval:n{#4-1}, #3|\int_eval:n{#4+1}|#3|#4}
    \tl_set:Nn \l_tmpc_tl {#3|#4} % last placed coordinate is #3|#4
}

% usage: \__aTableau_start_ribbon_path:nnnn r1|c1|r2|c2
% Add the next edge to \l__aTableau_ribbon_path_tl in  a (connected component
% of) the path around the boundary of a ribbon. To determine whether the next
% vertex is (r1,c1) or (r2,c2) we look at \l_tmpc_tl, which is the last vertex
% that was added. Finally,  update \l_tmpc_tl to point to the last vertex, and
% set \l__aTableau_adjacent_edges_clist equal to the list of possible adjacent
% edges that care connected to \l_tmpc_tl.
\cs_new_protected:Npn \__aTableau_add_to_ribbon_path:nnnn #1|#2|#3|#4|
{
    \tl_if_eq:VnTF \l_tmpc_tl {#1|#2}
    { % the last placed coordinate was #1|#2, so the new vertex is #3|#4
      \__aTableau_set_box_coordinates:nnn {a} {#3} {#4}
      \tl_put_right:Ne \l__aTableau_ribbon_path_tl {--(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)}
      \tl_set:Nn \l_tmpc_tl {#3|#4} % last vertex in boundary is #3|#4
      % create list of the edges that can connect to \l_tmpc_tl
      \clist_set:Ne \l__aTableau_adjacent_edges_clist { #3|#4|\int_eval:n{#3-1}|#4, \int_eval:n{#3+1}|#4|#3|#4, #3|#4|#3|\int_eval:n{#4-1}, #3|\int_eval:n{#4+1}|#3|#4}
    }
    { % the last placed coordinate was #3|#4, so the new vertex is #1|#2
      \__aTableau_set_box_coordinates:nnn {a} {#1} {#2}
      \tl_put_right:Ne \l__aTableau_ribbon_path_tl {--(\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)}
      \tl_set:Nn \l_tmpc_tl {#1|#2} % last vertex in boundary is #1|#2
      % create list of edges that can connect to \l_tmpc_tl
      \clist_set:Ne \l__aTableau_adjacent_edges_clist { #1|#2|\int_eval:n{#1-1}|#2, \int_eval:n{#1+1}|#2|#1|#2, #1|#2|#1|\int_eval:n{#2-1}, #1|\int_eval:n{#2+1}|#1|#2}
    }
}

% usage: \__aTableau_finalise_ribbon:
% The edges around the ribbon boundary are stored as keys in \l__aTableau_ribbon_prop.
% We pick any edge in the boundary, using \prop_map_inline:Nn, and then add a
% connected circuit of edges to \l__aTableau_ribbon_path_tl by searching for
% adjacent edges in \l__aTableau_ribbon_prop. As the boundary is a union of
% circles, if there are two connected edges it does not matter which edge we
% choose.
\cs_new_protected:Nn \__aTableau_finalise_ribbon:
{
    \tl_clear:N\l__aTableau_ribbon_path_tl  % will become the border of the ribbon
    \bool_do_while:nn { ! \prop_if_empty_p:N \l__aTableau_ribbon_prop }
    {
        % Use \prop_map_inline:Nn to find a key in \l__aTableau_ribbon_prop
        \prop_map_inline:Nn \l__aTableau_ribbon_prop
        {
            \tl_clear:N \l_tmpc_tl  % used to determine the next vertex
            \__aTableau_start_ribbon_path:nnnn ##1|
            \prop_remove:Ne \l__aTableau_ribbon_prop {##1} % remove the key
            \prop_map_break:
        }

        \bool_set_true:N \l_tmpa_bool
        % the path corresponding to the key k is k->ribbon_prop[k] -> ribbon_prop[ribbon_prop[k] -> ...
        \bool_do_while:Nn \l_tmpa_bool
        {
            \clist_map_inline:Nn \l__aTableau_adjacent_edges_clist
            {
                \bool_set_false:N \l_tmpa_bool
                \prop_get:NnNT \l__aTableau_ribbon_prop {##1} \l_tmpa_tl
                {
                    \bool_set_true:N \l_tmpa_bool  % found an edge
                    \__aTableau_add_to_ribbon_path:nnnn ##1|
                    \prop_remove:Ne \l__aTableau_ribbon_prop {##1} % remove the key
                    \clist_map_break:
                }
            }
        }
    }
}

% usage: \__aTableau_finalise_path:n
% We do not need to do anything to finalise a path
\cs_set_eq:NN \__aTableau_finalise_path:n \prg_do_nothing:

% by default, snobs have the same styles and coordinates as ribbons
\cs_set_eq:NN \__aTableau_extend_snob:n         \__aTableau_extend_ribbon:n
\cs_set_eq:NN \__aTableau_finalise_snob:        \__aTableau_finalise_ribbon:
\cs_set_eq:NN \__aTableau_initialise_snob_head: \__aTableau_initialise_ribbon_head:

% \__aTableau_draw_ribbon: use the various sequences we have constructed
% to draw the ribbon. We first place the nodes with default styling and
% no text, then draw the ribbon with its supplied style and then,
% finally, add the nodes with custom styling or text.
\cs_new_protected:Nn \__aTableau_draw_ribbon:
{
    % construct the ribbon boundary in \l__aTableau_ribbon_path_tl
    \use:c { __aTableau_finalise_ \l__aTableau_ribbon_type_tl :}

    % draw the ribbon boundary, applying any style
    \exp_last_unbraced:Ne \draw { [aTableau/\l__aTableau_ribbon_type_tl, \l__aTableau_ribbon_style_tl] } \l__aTableau_ribbon_path_tl;

    % Now we add the nodes in the ribbon with the default styling (or
    % unstyled), and styled nodes in the ribbon. To do this we build the two
    % token lists \l__aTableau_unstyled_nodes_tl and
    % \l__aTableau_styled_nodes_tl for these two types of nodes
    \tl_clear:N \l__aTableau_styled_nodes_tl
    \tl_clear:N \l__aTableau_unstyled_nodes_tl
    % use ribbon_box, snob_box or path_box
    \tl_set_eq:Nc \l_tmpc_tl { l__aTableau_\l__aTableau_ribbon_type_tl  _box_tl }

    \seq_map_inline:Nn \l__aTableau_styles_seq
    {
        % The last text entry in \l__aTableau_texts_seq will be empty if the
        % peeking stops before it can be cleared
        \seq_pop_left:NNF \l__aTableau_texts_seq \l_tmpa_tl {\tl_clear:N\l_tmpa_tl}

        % pop the text and row and column indices
        \seq_pop_left:NN \l__aTableau_rcs_seq \l_tmpb_tl   % row index
        \int_set:Nn \l__aTableau_row_int {\l_tmpb_tl}
        \seq_pop_left:NN \l__aTableau_rcs_seq \l_tmpb_tl   % column index
        \int_set:Nn \l__aTableau_col_int {\l_tmpb_tl}

        % compute the box coordinates
        \__aTableau_set_box_coordinates:nVV {l} \l__aTableau_row_int \l__aTableau_col_int
        \tl_if_empty:oTF {##1\l_tmpa_tl}
        {   % nodes with default style and no text are added to \l__aTableau_unstyled_nodes_tl
            \__aTableau_put_tikz_node:neVnV {unstyled_nodes} {aTableau/\l__aTableau_ribbon_type_tl Box,\l__aTableau_ribbon_style_tl} \l__aTableau_name_tl {l} \l_tmpc_tl
        }
        {   % nodes with styling are added to \l__aTableau_styled_nodes_tl
            \__aTableau_put_tikz_node:neVnV {styled_nodes} {aTableau/\l__aTableau_ribbon_type_tl Box,\l__aTableau_ribbon_style_tl,##1} \l__aTableau_name_tl {l} \l_tmpa_tl
        }
    }

    % finally, add the unstyled and the styled nodes on top of the ribbon
    \l__aTableau_unstyled_nodes_tl
    \l__aTableau_styled_nodes_tl
}

% ---------------------------------------------------------------------------
% abacuses

% usage: __aTableau_abacus_bead:nnn {style} {coord} {entry}
% Draw an abacus bead given its style, coordinates (x#2_fp, y#2_fp), and entry
\cs_new_protected:Npn \__aTableau_abacus_bead:nnn #1 #2 #3
{
    \node[aTableau/abacusBead, #1] at (\fp_use:c{l__aTableau_x#2_fp},\fp_use:c{l__aTableau_y#2_fp}) {\__aTableau_entry:n {#3}};
}

% usage: \__aTableau_standard_end:nnnn {style} {initial coordinate} {offset} {±1}
% Draw a line for an abacus runner using the initial coordinate (x#2_fp, y#2_fp) -- +(x#3_fp,y#3_fp)
\cs_new_protected:Npn \__aTableau_standard_end:nnnn #1 #2 #3 #4
{
    \draw[aTableau/#1] (\fp_use:c {l__aTableau_x#2_fp},\fp_use:c {l__aTableau_y#2_fp})--+(\fp_use:c {l__aTableau_x#3_fp}, \fp_use:c {l__aTableau_y#3_fp});
}

% usage: \__aTableau_standard_runner:nnn {style} {initial coordinate} {offset}
% Draw a line for an abacus runner using the initial coordinate (x#2_fp, y#2_fp) -- +(x#3_fp,y#3_fp)
\cs_new_protected:Npn \__aTableau_standard_runner:nnn #1 #2 #3
{
    \draw[aTableau/#1] (\fp_use:c {l__aTableau_x#2_fp}, \fp_use:c {l__aTableau_y#2_fp})--+(\fp_use:c {l__aTableau_x#3_fp}, \fp_use:c{l__aTableau_y#3_fp});
}

% usage: \__aTableau_traditional_end:nnnn {style} {initial coordinate} {offset} {±1}
% Draw a rod for an abacus runner using the initial coordinate (x#2_fp, y#2_fp) -- +(x#3_fp,y#3_fp)
\cs_new_protected:Npn \__aTableau_traditional_end:nnnn #1 #2 #3 #4
{
    \draw[aTableau/#1]
        (\fp_use:c{l__aTableau_x#2_fp}, \fp_use:c{l__aTableau_y#2_fp})
        rectangle
        +(\fp_eval:n {\fp_use:c{l__aTableau_x#3_fp}-#4*\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dx_fp},
          \fp_eval:n {\fp_use:c{l__aTableau_y#3_fp}-#4*\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dy_fp});
}

% usage: \__aTableau_traditional_runner:nnn {style} {initial coordinate} {offset}
% Draw a rod for an abacus runner using the initial coordinate (x#2_fp, y#2_fp) -- +(x#3_fp,y#3_fp)
\cs_new_protected:Npn \__aTableau_traditional_runner:nnn #1 #2 #3
{
    \shadedraw[aTableau/#1]
        (\fp_eval:n {\fp_use:c{l__aTableau_x#2_fp}+\l__aTableau_tick_length_fp*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp},
             \fp_eval:n {\fp_use:c{l__aTableau_y#2_fp}+\l__aTableau_tick_length_fp*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp})
        rectangle
        +(\fp_eval:n {\fp_use:c{l__aTableau_x#3_fp}-2*\l__aTableau_tick_length_fp*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp},
          \fp_eval:n {\fp_use:c{l__aTableau_y#3_fp}-2*\l__aTableau_tick_length_fp*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp});
}

% usage: \__aTableau_draw_runner_ends:nnnn {symbol} {style} {row index} {±1}
\cs_new_protected:Npn \__aTableau_draw_runner_ends:nnnn #1 #2 #3 #4
{
    \bool_if:NT \l__aTableau_traditional_bool
    {
        \tl_if_in:nVF {-_|} #1 { \msg_warning:nnV {aTableau} {unsupported-abacus-end} #1 }
    }
    \int_step_inline:nnn {0} {\l__aTableau_cols_int-1}
    {
        \seq_if_in:NeF \l__aTableau_dotted_cols_seq { ##1 }
        {
            % draw the abacus runners from (xa,ya) to (xb,yb)
            \__aTableau_set_bead_coordinates:nnn {a} { #3-#4*0.9 } { ##1 }
            % set the length
            \fp_set:Nn \l__aTableau_xb_fp {#4*0.9*\l__aTableau_abacus_row_dx_fp*\l__aTableau_abacus_wd_fp}
            \fp_set:Nn \l__aTableau_yb_fp {#4*0.9*\l__aTableau_abacus_row_dy_fp*\l__aTableau_abacus_ht_fp}
            \__aTableau_draw_abacus_runner:nnn {clearBoxes}    {a} {b}
            \__aTableau_draw_abacus_runner:nnn {abacusEnds,#2} {a} {b}
        }
    }
    % set default "row height" of the runner labels for use below
    \fp_set:Nn \l_tmpa_fp {#3-1.4}
}

% usage: \__aTableau_add_abacus_end:Vnn {abacus_top/abacus_bottom} {row index} {±1}
% Draw the top/bottom on the abacus. Here:
%   - #1 is either % \l__aTableau_abacus_top_tl  or \l__aTableau_abacus_bottom_tl
%   - #2 is the row index for the line we will draw
%   - #3, which is ±1, determines whether arrows go up or down
% either 0, for top, or the row index of the last row, for bottom
\cs_new_protected:Npn \__aTableau_add_abacus_end:Vnn #1 #2 #3
{
    \str_case:Vn #1
    {
        {-}  % draw a line from the just before the first runner to just after the last runner
            {
                \__aTableau_set_bead_coordinates:nnn {a} {#2} {-0.5}
                \fp_set:Nn \l__aTableau_xb_fp {\l__aTableau_cols_int*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp}
                \fp_set:Nn \l__aTableau_yb_fp {\l__aTableau_cols_int*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp}
                \__aTableau_draw_abacus_end:nnnn {abacusEnds} {a} {b} {#3}
                % set default "row height" of the runner labels for use below
                \fp_set:Nn \l_tmpa_fp {#2-0.4}
            }

        {_}  % draw a line  from the first to last runner
            {
                \bool_if:NTF \l__aTableau_traditional_bool
                {
                    \__aTableau_set_bead_coordinates:nnn {a} {#2} {-\l__aTableau_tick_length_fp}
                    \fp_set:Nn \l__aTableau_xb_fp {(\l__aTableau_cols_int-1+2*\l__aTableau_tick_length_fp)*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp}
                    \fp_set:Nn \l__aTableau_yb_fp {(\l__aTableau_cols_int-1+2*\l__aTableau_tick_length_fp)*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp}
                }
                {
                    \__aTableau_set_bead_coordinates:nnn {a} {#2} {0}
                    \fp_set:Nn \l__aTableau_xb_fp {(\l__aTableau_cols_int-1)*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp}
                    \fp_set:Nn \l__aTableau_yb_fp {(\l__aTableau_cols_int-1)*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp}
                }
                \__aTableau_draw_abacus_end:nnnn {abacusEnds} {a} {b} {#3}
                % set default "row height" of the runner labels for use below
                \fp_set:Nn \l_tmpa_fp {#2-0.4}
            }

        {.} % draw dots ...
            { \__aTableau_draw_runner_ends:nnnn {#1} {dotted} {#2} {#3} }


        {>} % draw arrow --> except for the dotted columns
            { \__aTableau_draw_runner_ends:nnnn {#1} {<-} {#2} {#3} }


        {*} % draw dotted arrow ...->
            { \__aTableau_draw_runner_ends:nnnn {#1} {dotted,<-} {#2} {#3} }


        {|} {} % no extra decoration  => do nothing

    }
}


% usage: \__aTableau_abacus_row_labels:
% Add nodes containing the row labels
\cs_new_protected:Nn \__aTableau_abacus_row_labels:
{
        % add the runner labels using the "row height" #1, which is set by \__aTableau_draw_abacus_end:Vnn
        \int_zero:N \l__aTableau_r_int
        \seq_map_inline:Nn \l__aTableau_row_labels_seq
        {
            \__aTableau_set_bead_coordinates:nVn {a} \l__aTableau_r_int {-1}
            \seq_if_in:NVF \l__aTableau_dotted_rows_seq \l__aTableau_r_int
            {
                \node[aTableau/rowLabel] at (\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp){ \__aTableau_entry:n{##1} };
            }
            \int_incr:N \l__aTableau_r_int
        }
}

% usage: \__aTableau_abacus_runner_labels:n { height }
% Add nodes containing the runner labels
\cs_new_protected:Npn \__aTableau_abacus_runner_labels:n #1
{
        % add the runner labels using the "row height" #1, which is set by \__aTableau_draw_abacus_end:Vnn
        \int_zero:N \l__aTableau_c_int
        \seq_map_inline:Nn \l__aTableau_runner_labels_seq
        {
            \__aTableau_set_bead_coordinates:nnV {a} {#1} \l__aTableau_c_int
            \seq_if_in:NVF \l__aTableau_dotted_cols_seq \l__aTableau_c_int
            {
                \node[aTableau/runnerLabel] at (\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp){ \__aTableau_entry:n{##1} };
            }
            \int_incr:N \l__aTableau_c_int
        }
}

% usage: \__aTableau_extend_beta_numbers:
% This is a helper function to when parsing gthe abacus bead specifications. It
% extends the length of the partition, or sequence of beta numbers, if it is
% shorter than \l__aTableau_beads_int by appropriately modifying the sequences
% \l__aTableau_rcs_seq, \l__aTableau_texts_seq and \l__aTableau_styles_seq
\cs_new_protected:Nn \__aTableau_extend_beta_numbers:
{
    \tl_set:No \l_tmpa_tl { \seq_count:N \l__aTableau_rcs_seq }
    \int_compare:nNnTF {\l__aTableau_beads_int} > {\l_tmpa_tl}
    {
        \bool_if:nTF \l__aTableau_beta_numbers_bool
        {   % need to increment the existing beta numbers
            \seq_set_eq:NN \l_tmpa_seq \l__aTableau_rcs_seq
            \int_set:Nn \l_tmpa_int { \l__aTableau_beads_int-\l_tmpa_tl }
            \seq_clear:N \l__aTableau_rcs_seq
            \seq_map_inline:Nn \l_tmpa_seq
            {
                \seq_put_right:Ne \l__aTableau_rcs_seq { \int_eval:n {##1+\l_tmpa_int}}
            }
            % now need to add some more beads
            \int_step_inline:nnnn {\l_tmpa_int-1} (-1) {0}
            {
              \seq_put_right:Nn \l__aTableau_rcs_seq   {##1}
              \seq_put_right:Nn \l__aTableau_texts_seq  {}
              \seq_put_right:Nn \l__aTableau_styles_seq {}
            }
        }
        {   % need to add extra zeros to the partition
            \int_step_inline:nnn {\l_tmpa_tl} {\l__aTableau_beads_int-1}
            {
              \seq_put_right:Nn \l__aTableau_rcs_seq   {0}
              \seq_put_right:Nn \l__aTableau_texts_seq  {}
              \seq_put_right:Nn \l__aTableau_styles_seq {}
            }
        }
    }
    {   % give a warning if there are more beads than l__aTableau_beads_int
        \bool_if:nT { \int_compare_p:nNn {\l__aTableau_beads_int} > {0}
                       && \int_compare_p:nNn {\l__aTableau_beads_int} < {\l_tmpa_tl}
        }
        {
            \msg_warning:nne {aTableau} {many-beads} \l_tmpa_tl
            \int_set:NV \l__aTableau_beads_int \l_tmpa_tl
        }
    }
}

% usage: \__aTableau_abacus:nn { #runners } { bead specifications }
% This is the main routine for the \Abacus command. It parses the abacus
% specifications by calling the peeking functions above, then draws the
% runners, frame and beads
\cs_new_protected:Npn \__aTableau_abacus:nn #1 #2
{
    % set the star style
    \tl_set:Nn \l__aTableau_starstyle_tl {aTableau/abacusStar}

    % record the number of runners in cols_int
    \int_set:Nn \l__aTableau_cols_int {#1}

    \seq_if_empty:NF \l__aTableau_runner_labels_seq
    {
        \int_set:No \l_tmpa_int { \seq_count:N \l__aTableau_runner_labels_seq }
        \int_compare:nNnF {\l__aTableau_cols_int-\l_tmpa_int } = {0}
        {
            \msg_error:nn {aTableau} {missing-runner-labels }
        }
    }

    % Extract the bead positions and their styles into
    % \l__aTableau_shape_seq, \l__aTableau_styles_seq and \l__aTableau_text_tl
    % We allow all of the following expressions
    %   m, m^r, [style]m, [style]m^2, *m, *m^2,
    %   m_text, m_text^r, [style]m_text, [style]m_text^2, *m_text, *m_text^2,
    %   m_text^r, [style]m_text^2, *m_text^2
    % where m is a part of the partition \l__aTableau_shape_seq and r is
    % its' exponent. First, clear all of the sequences and zero the bead
    % counter
    \seq_clear:N \l__aTableau_shape_seq  % will hold the beta numbers
    \seq_clear:N \l__aTableau_styles_seq % the bead style
    \seq_clear:N \l__aTableau_texts_seq  % the bead text
    \int_zero:N  \l__aTableau_row_int    % the maximum beta number

    % Read the partition/beta numbers and the style settings. The beta numbers
    % for the abacus are first placed in \l__aTableau_rcs_seq by the peeking
    % routines, after which they are converted to beta numbers, if needed, and
    % stored in \l__aTableau_shape_seq

    % look for a | to see if we have been given the partition/beta numbers as a quotient
    \tl_if_in:nnTF {#2} {|}
    {   % abacus is specified as |-separated list that gives the quotient

        % store the components in \l__aTableau_component_seq
        \seq_set_split:Nnn \l__aTableau_component_seq {|} {#2}

        % check that we do not have too many components
        \int_compare:oNnT { \seq_count:N \l__aTableau_component_seq } > {\l__aTableau_cols_int}
        {
            \msg_error:nnx {aTableau} { quotient-components } { \seq_count:N \l__aTableau_component_seq > #1  }
        }

        \int_zero:N \l__aTableau_c_int % offset for each runner
        \seq_map_inline:Nn \l__aTableau_component_seq
        {
            \seq_clear:N \l__aTableau_rcs_seq

            \tl_if_blank:nF {##1}
            {
                % add the entries in the quotient, peeking between commas
                \__aTableau_peek_style:nw {record_style:nn} ##1 \q_recursion_tail \q_recursion_stop

                % Extend the length of the partition, or sequence of beta numbers, if
                % it is shorter than \l__aTableau_beads_int
                \__aTableau_extend_beta_numbers:

                % convert the sequence in \l__aTableau_rcs_seq into beta numbers and add to \l__aTableau_shape_seq
                \bool_if:NTF \l__aTableau_beta_numbers_bool
                {   % entered as beta numbers, so just need to multiply by e = \l__aTableau_cols_int
                    \int_set:No \l_tmpa_int { \l__aTableau_cols_int * (\seq_item:Nn \l__aTableau_rcs_seq {1})} % maximum beta number
                    \seq_map_inline:Nn \l__aTableau_rcs_seq
                    { \seq_put_right:Ne \l__aTableau_shape_seq { \int_eval:n {\l__aTableau_c_int+\l__aTableau_cols_int*####1} } }
                }
                {   % convert the partition to beta numbers
                    \tl_set:No \l_tmpa_tl {\seq_count:N \l__aTableau_rcs_seq} % number of beads
                    \int_set:Nn \l__aTableau_r_int {\l_tmpa_tl-1}% beta number offset for each part
                    \tl_set:No \l_tmpb_tl { \seq_item:Nn \l__aTableau_rcs_seq {1} } % first part in quotient
                    \int_set:Nn \l_tmpa_int { \l__aTableau_c_int+\l__aTableau_cols_int*(\l__aTableau_r_int+\l_tmpb_tl) } % maximum beta number
                    \seq_map_inline:Nn \l__aTableau_rcs_seq
                    {
                        \seq_put_right:Ne \l__aTableau_shape_seq { \int_eval:n { \l__aTableau_c_int+\l__aTableau_cols_int*(####1+\l__aTableau_r_int) } }
                        \int_decr:N \l__aTableau_r_int
                    }
                }

                % check that the maximum beta number has not changed
                \int_compare:oNnT { \l_tmpa_int } > { \l__aTableau_row_int }
                {
                    \int_set_eq:NN \l__aTableau_row_int \l_tmpa_int
                }
            }

            % increment runner offset for next component
            \int_incr:N \l__aTableau_c_int
        }
        \int_set:No \l__aTableau_beads_int { \seq_count:N \l__aTableau_shape_seq }
    }
    {   % abacus is specified by a partition/sequence of beta numbers

        % Parse #2 into the rows and columns with style. Initially we used
        % \seq_set_split:Nnn here, but this required escaping complex style
        % specifications as [{...}]. Now we peek for the commas and the style
        % simultaneously.
        \seq_clear:N \l__aTableau_rcs_seq   % initially store beta numbers here and then move to \l__aTableau_shape_seq
        \tl_if_blank:nF {#2}
        {
            \__aTableau_peek_style:nw {record_style:nn} #2 \q_recursion_tail \q_recursion_stop

            % Extend the length of the partition, or sequence of beta numbers, if
            % it is shorter than \l__aTableau_beads_int
            \__aTableau_extend_beta_numbers:
            \int_set:No \l__aTableau_beads_int { \seq_count:N \l__aTableau_rcs_seq }

            % convert \l__aTableau_rcs_seq to beta numbers in \l__aTableau_shape_seq
            \bool_if:NTF \l__aTableau_beta_numbers_bool
            {
                \seq_set_eq:NN \l__aTableau_shape_seq \l__aTableau_rcs_seq
            }
            {
                \int_set:Nn \l__aTableau_r_int {\l__aTableau_beads_int-1} % the offset to the beta numbers in each row
                \seq_map_inline:Nn \l__aTableau_rcs_seq
                {
                    \seq_put_right:Ne \l__aTableau_shape_seq { \int_eval:n { ##1 + \l__aTableau_r_int } }
                    \int_decr:N \l__aTableau_r_int
                }
            }
            % set the maximum beta number
            \int_set:No \l__aTableau_row_int { 0\seq_item:Nn \l__aTableau_shape_seq {1}}
        }
    }


    % determine the number of abacus rows required
    \int_set:Nn \l_tmpa_int { 1+\int_div_truncate:nn {\l__aTableau_row_int} {#1} }
    \int_compare:nNnT {\l__aTableau_rows_int} < { \l_tmpa_int }
    {
        % give a warning if we need more rows than \l__aTableau_rows_int
        \int_compare:nNnT {\l__aTableau_rows_int} > {0}
        { \msg_warning:nne {aTableau} {many-rows} {\int_use:N\l_tmpa_int} }

        % set the number of rows
        \int_set_eq:NN \l__aTableau_rows_int \l_tmpa_int
    }

    % draw the abacus runners
    \int_zero:N \l__aTableau_col_int
    % compute the change in x and y coordinates when we move to the end of the runner,
    % which we store as (xl_fp, yl_fp) for use in \__aTableau_draw_abacus_runner:nnn
    \fp_set:Nn \l_tmpa_fp {\l__aTableau_abacus_top_fp+\l__aTableau_rows_int+\l__aTableau_abacus_bottom_fp-0.5}
    \fp_set:Nn \l__aTableau_xl_fp { \l_tmpa_fp*\l__aTableau_abacus_row_dx_fp*\l__aTableau_abacus_wd_fp }
    \fp_set:Nn \l__aTableau_yl_fp { \l_tmpa_fp*\l__aTableau_abacus_row_dy_fp*\l__aTableau_abacus_ht_fp }
    % similarly, set (xb_fp, yb_fp) to the half the tick length
    \fp_set:Nn \l__aTableau_xb_fp {\l__aTableau_tick_length_fp*\l__aTableau_abacus_col_dx_fp}
    \fp_set:Nn \l__aTableau_yb_fp {\l__aTableau_tick_length_fp*\l__aTableau_abacus_col_dy_fp}
    \int_step_inline:nnn {0} {\l__aTableau_cols_int-1}
    {
        % skip the runners in dotted_cols_seq
        \seq_if_in:NnF \l__aTableau_dotted_cols_seq {##1}
        {
            % draw the abacus runners from (xa,ya) to +(xl,yl)
            \__aTableau_set_bead_coordinates:nnn {a} {-\l__aTableau_abacus_top_fp} {##1}
            \__aTableau_draw_abacus_runner:nnn {abacusRunner} {a} {l}

            % draw ticks
            \int_step_inline:nnn {0} {\l__aTableau_rows_int-1}
            {
                \__aTableau_set_bead_coordinates:non {a} { ####1 } {##1}
                % add a named node first that sits on the runner
                \node[aTableau/namedTick] (\l__aTableau_name_tl) at (\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp){};
                % draw the tick, which extends to either side of the runner
                \draw[aTableau/abacusTick]
                    (\fp_eval:n{\l__aTableau_xa_fp+\l__aTableau_xb_fp},\fp_eval:n{\l__aTableau_ya_fp+\l__aTableau_yb_fp})
                    --(\fp_eval:n{\l__aTableau_xa_fp-\l__aTableau_xb_fp},\fp_eval:n{\l__aTableau_ya_fp-\l__aTableau_yb_fp});
            }
        }
    }

    \bool_if:NT \l__aTableau_framed_bool
    {
        % draw extra runners at the left and right when framed is true
        \__aTableau_set_bead_coordinates:nnn {b} {-\l__aTableau_abacus_top_fp} {\l__aTableau_cols_int-0.4}
        \__aTableau_set_bead_coordinates:nnn {a} {-\l__aTableau_abacus_top_fp} {-0.6}
        \bool_if:NTF \l__aTableau_traditional_bool
        {
            % need to move the ends up by \l__aTableau_beam_height_fp
            \fp_add:Nn \l__aTableau_xa_fp {-\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dx_fp}
            \fp_add:Nn \l__aTableau_ya_fp {-\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dy_fp}
            \fp_add:Nn \l__aTableau_xb_fp {-\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dx_fp}
            \fp_add:Nn \l__aTableau_yb_fp {-\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dy_fp}
            % need to make the runners slightly longer
            \fp_add:Nn \l__aTableau_xl_fp {2*\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dx_fp}
            \fp_add:Nn \l__aTableau_yl_fp {2*\l__aTableau_beam_height_fp*\l__aTableau_abacus_row_dy_fp}

        }
        {
            \fp_set:Nn \l_tmpb_fp {-\l__aTableau_abacus_top_fp}
        }
        % draw the extra runners
        \__aTableau_draw_abacus_runner:nnn {abacusRunner} {a} {l}
        \__aTableau_draw_abacus_runner:nnn {abacusRunner} {b} {l}

        % use the wide abacus ends
        \__aTableau_set_abacus_ends:nn --
    }

    % draw the top of the abacus
    \__aTableau_add_abacus_end:Vnn \l__aTableau_abacus_top_tl {-\l__aTableau_abacus_top_fp} {1}

    % add any abacus runner labels
    \bool_if:NT \l__aTableau_traditional_bool
    {
        \fp_add:Nn \l_tmpa_fp {-\l__aTableau_beam_height_fp/\l__aTableau_abacus_ht_fp}
    }
    \__aTableau_abacus_runner_labels:n \l_tmpa_fp

    % add any abacus row labels
    \__aTableau_abacus_row_labels:

    % draw the bottom of the abacus
    \__aTableau_add_abacus_end:Vnn \l__aTableau_abacus_bottom_tl {\l__aTableau_rows_int+\l__aTableau_abacus_bottom_fp-0.5} {-1}

    % set e when entries=residues
    \tl_if_eq:VnT \l__aTableau_show_tl {residues}
    {
          % if it is not set already, then set e based on the number of runners and the Cartan type
          \int_compare:nNnF {\l__aTableau_e_int} < {1000}
          {
              \str_case:Vn \l__aTableau_cartan_tl
              {
                  {A}  { \int_set_eq:NN \l__aTableau_e_int \l__aTableau_cols_int         }
                  {C}  { \int_set:Nn    \l__aTableau_e_int {\l__aTableau_cols_int/2}     }
                  {AA} { \int_set:Nn    \l__aTableau_e_int {(\l__aTableau_cols_int-1)/2} }
                  {DD} { \int_set:Nn    \l__aTableau_e_int {\l__aTableau_cols_int/2-1}   }
              }
          }
    }

    % for certain entries, we need the beta numbers to be correctly sorted, so we set this up now
    \tl_if_blank:VF \l__aTableau_show_tl
    {
        % put the beta numbers into \l__aTableau_rcs_seq and sort them
        \seq_set_eq:NN \l__aTableau_rcs_seq \l__aTableau_shape_seq
        \seq_sort:Nn \l__aTableau_rcs_seq
        {   % put into decreasing order
            \int_compare:nNnTF { ##1 } < { ##2 }
            { \sort_return_swapped: }
            { \sort_return_same: }
        }
    }

    % draw the beads on the abacus last so that they are on top
    \int_step_inline:nn { \l__aTableau_beads_int } % finally, add the beads, with labels and styles
    {
        \int_set:No \l_tmpa_int { \seq_item:Nn \l__aTableau_shape_seq {##1} } % beta number
        \int_set:Nn \l__aTableau_row_int { \int_div_truncate:nn {\l_tmpa_int} {\l__aTableau_cols_int} }
        \int_set:Nn \l__aTableau_col_int { \int_mod:nn {\l_tmpa_int} {\l__aTableau_cols_int} }

        % determine the bead coordinates: push everything 0.5 of a
        % unit down to allow space of the top of the abacus
        \__aTableau_set_bead_coordinates:nVV {l} \l__aTableau_row_int \l__aTableau_col_int

        % add the bead only if they are not in a dotted row or column
        \seq_if_in:NVF \l__aTableau_dotted_rows_seq \l__aTableau_row_int
        {
            \seq_if_in:NVF \l__aTableau_dotted_cols_seq \l__aTableau_col_int
            {
                % check to see if we need to show something for this entry
                \str_case:VnF \l__aTableau_show_tl
                {
                  {beads}
                      { \tl_set:No \l_tmpa_tl { \int_use:N\l_tmpa_int } }
                  {residues}
                      {
                           \int_set:Nn \l_tmpb_int { \l__aTableau_charge_int+\l_tmpa_int-\l__aTableau_beads_int }
                           \tl_set:No \l_tmpa_tl { \__aTableau_residue:nn {\l_tmpb_int} {\l__aTableau_e_int} }
                      }
                  {rows}
                      {
                          % find the correct index of the beta number \l_tmpa_int in
                          \int_zero:N \l__aTableau_r_int
                          \tl_set:No \l_tmpa_tl {\int_use:N \l_tmpa_int}
                          \seq_map_inline:Nn \l__aTableau_rcs_seq
                          {
                              \int_incr:N \l__aTableau_r_int
                              \tl_if_eq:VnT \l_tmpa_tl {####1}
                              {
                                  \int_set:Nn \l_tmpb_int { \l_tmpa_int+\l__aTableau_r_int-\l__aTableau_beads_int } % corresponding part
                                  \int_compare:nNnTF {\l_tmpb_int} > {0}
                                  { \tl_set:No \l_tmpa_tl { \int_use:N \l__aTableau_r_int } }
                                  { \tl_set:No \l_tmpa_tl { {-} } }
                                  \seq_map_break:
                              }
                           }
                      }
                  {shape}
                      {
                          % find the correct index of the beta number \l_tmpa_int in
                          \int_zero:N \l__aTableau_r_int
                          \tl_set:No \l_tmpa_tl {\int_use:N \l_tmpa_int}
                          \seq_map_inline:Nn \l__aTableau_rcs_seq
                          {
                              \int_incr:N \l__aTableau_r_int
                              \tl_if_eq:VnT \l_tmpa_tl {####1}
                              {
                                  \tl_set:No \l_tmpa_tl { \int_eval:n {\l_tmpa_int+\l__aTableau_r_int-\l__aTableau_beads_int }  }% corresponding part
                                  \seq_map_break:
                              }
                          }
                      }
                  {     }
                      { \tl_set:No \l_tmpa_tl { \seq_item:Nn \l__aTableau_texts_seq {##1} } }
                }
                { \msg_error:nnx {aTableau} {unrecognised-abacus-label} {  \l__aTableau_show_tl } }
                % draw the bead with style
                \tl_set:No \l_tmpb_tl { \seq_item:Nn \l__aTableau_styles_seq {##1} }
                \__aTableau_abacus_bead:enV \l_tmpb_tl {l} \l_tmpa_tl
            }
        }
    }

    % remove dotted rows and columns
    \seq_if_empty:NF \l__aTableau_dotted_rows_seq \__aTableau_remove_dotted_abacus_rows:
    \seq_if_empty:NF \l__aTableau_dotted_cols_seq \__aTableau_remove_dotted_abacus_cols:
}

% usage: \__aTableau_remove_dotted_abacus_cols:
% Add dots to the columns of the abacus in \l__aTableau_dotted_cols_seq
\cs_new_protected:Nn \__aTableau_remove_dotted_abacus_cols:
{
    % shift in row-direction
    \fp_set:Nn \l_tmpa_fp {\l__aTableau_abacus_row_dx_fp*\l__aTableau_abacus_wd_fp}
    \fp_set:Nn \l_tmpb_fp {\l__aTableau_abacus_row_dy_fp*\l__aTableau_abacus_ht_fp}

    % take a copy of \l__aTableau_dotted_cols_seq so that the pop_left's below
    % do not destroy it
    \seq_set_eq:NN \l_tmpb_seq \l__aTableau_dotted_cols_seq
    \bool_do_until:nn { \seq_if_empty_p:N \l_tmpb_seq }
    {
        \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
        \int_set:Nn \l__aTableau_col_int {\l_tmpa_tl}
        \int_set:Nn \l__aTableau_c_int {\l__aTableau_col_int+1}

        % LaTeX3 does not provide \seq_if_in_p:NN, so ...
        \bool_set_true:N \l_tmpa_bool
        \bool_do_while:nn { \l_tmpa_bool }
        {
            \int_set:Nn \l_tmpa_int {0\seq_item:Nn \l_tmpb_seq 1}
            \int_compare:nNnTF {\l__aTableau_c_int} = {\l_tmpa_int}
            {
              \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
              \int_incr:N \l__aTableau_c_int
            }
            { \bool_set_false:N \l_tmpa_bool }
        }

        \__aTableau_set_bead_coordinates:nnV {l} { 0 } \l__aTableau_col_int

        % set (xa,ya) and (xb,yb) to the "left" and "right" hand coordinates that we want remove
        \fp_set:Nn \l__aTableau_xa_fp {\l__aTableau_xl_fp-0.35*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp}
        \fp_set:Nn \l__aTableau_ya_fp {\l__aTableau_yl_fp-0.35*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp}
        \fp_set:Nn \l__aTableau_xb_fp {\l__aTableau_xl_fp+(\l__aTableau_c_int-\l__aTableau_col_int-0.65)*\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp}
        \fp_set:Nn \l__aTableau_yb_fp {\l__aTableau_yl_fp+(\l__aTableau_c_int-\l__aTableau_col_int-0.65)*\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp}

        % blank out any line at the top of the abacus and replace it with dots
        \tl_if_in:nVT {-_} \l__aTableau_abacus_top_tl
        {
            \draw[aTableau/clearBoxes]
                (\fp_eval:n{\l__aTableau_xa_fp-0.1*\l_tmpa_fp}, \fp_eval:n{\l__aTableau_ya_fp-0.1*\l_tmpb_fp})
                --++(\fp_eval:n{0.2*\l_tmpa_fp},\fp_eval:n{0.2*\l_tmpb_fp})
                --(\fp_eval:n{\l__aTableau_xb_fp+0.1*\l_tmpa_fp}, \fp_eval:n{\l__aTableau_yb_fp+0.1*\l_tmpb_fp})
                --++(\fp_eval:n{-0.2*\l_tmpa_fp},\fp_eval:n{-0.2*\l_tmpb_fp})
                    --cycle
            ;
            % replace line with dots
            \draw[aTableau/dottedLine](\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp);
        }

        % now draw the dots
        \int_zero:N \l__aTableau_row_int
        \int_step_inline:nn {\l__aTableau_rows_int-1}
        {
            \fp_add:Nn \l__aTableau_xa_fp {\l_tmpa_fp}
            \fp_add:Nn \l__aTableau_ya_fp {\l_tmpb_fp}
            \fp_add:Nn \l__aTableau_xb_fp {\l_tmpa_fp}
            \fp_add:Nn \l__aTableau_yb_fp {\l_tmpb_fp}
            \draw[aTableau/dottedLine](\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp);
        }

        % blank out any line at the bottom of the abacus and replace it with dots
        \tl_if_in:nVT {-_} \l__aTableau_abacus_bottom_tl
        {
            \fp_add:Nn \l__aTableau_xa_fp {\l_tmpa_fp}
            \fp_add:Nn \l__aTableau_ya_fp {\l_tmpb_fp}
            \fp_add:Nn \l__aTableau_xb_fp {\l_tmpa_fp}
            \fp_add:Nn \l__aTableau_yb_fp {\l_tmpb_fp}
            \draw[aTableau/clearBoxes]
            (\fp_eval:n{\l__aTableau_xa_fp-0.1*\l_tmpa_fp}, \fp_eval:n{\l__aTableau_ya_fp-0.1*\l_tmpb_fp})
            --++(\fp_eval:n{0.2*\l_tmpa_fp},\fp_eval:n{0.2*\l_tmpb_fp})
            --(\fp_eval:n{\l__aTableau_xb_fp+0.1*\l_tmpa_fp}, \fp_eval:n{\l__aTableau_yb_fp+0.1*\l_tmpb_fp})
            --++(\fp_eval:n{-0.2*\l_tmpa_fp},\fp_eval:n{-0.2*\l_tmpb_fp})
                --cycle
            ;
            % first blank out the possible header line and replace with dots
            \draw[aTableau/dottedLine](\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp);
        }
    }
}

% usage: \__aTableau_remove_dotted_abacus_rows:
% Add dots to the rows of the abacus in \l__aTableau_dotted_rows_seq
\cs_new_protected:Nn \__aTableau_remove_dotted_abacus_rows:
{
      % shift in column-direction
      \fp_set:Nn \l_tmpa_fp {\l__aTableau_abacus_col_dx_fp*\l__aTableau_abacus_wd_fp}
      \fp_set:Nn \l_tmpb_fp {\l__aTableau_abacus_col_dy_fp*\l__aTableau_abacus_ht_fp}

      % take a copy of \l__aTableau_dotted_rows_seq so that the pop_left's below
      % do not destroy it
      \seq_set_eq:NN \l_tmpb_seq \l__aTableau_dotted_rows_seq
      \bool_do_until:nn { \seq_if_empty_p:N \l_tmpb_seq }
      {
          \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
          \int_set:Nn \l__aTableau_row_int {\l_tmpa_tl}
          \int_set:Nn \l__aTableau_r_int {\l__aTableau_row_int+1}

          % LaTeX3 does not provide \seq_if_in_p:NN, so ...
          \bool_set_true:N \l_tmpa_bool
          \bool_do_while:nn { \l_tmpa_bool }
          {
              \int_set:Nn \l_tmpa_int {0\seq_item:Nn \l_tmpb_seq 1}
              \int_compare:nNnTF {\l__aTableau_r_int} = {\l_tmpa_int}
              {
                \seq_pop_left:NN \l_tmpb_seq \l_tmpa_tl
                \int_incr:N \l__aTableau_r_int
              }
              { \bool_set_false:N \l_tmpa_bool }
          }

          \__aTableau_set_bead_coordinates:nnn {l} { \l__aTableau_row_int } { 0 }

          % set (xa,ya) and (xb,yb) to the "left" and "right" hand coordinates that we want remove
          \fp_set:Nn \l__aTableau_xa_fp {\l__aTableau_xl_fp-0.35*\l__aTableau_abacus_row_dx_fp*\l__aTableau_abacus_wd_fp}
          \fp_set:Nn \l__aTableau_ya_fp {\l__aTableau_yl_fp-0.35*\l__aTableau_abacus_row_dy_fp*\l__aTableau_abacus_ht_fp}
          \fp_set:Nn \l__aTableau_xb_fp {\l__aTableau_xl_fp+(\l__aTableau_r_int-\l__aTableau_row_int-0.65)*\l__aTableau_abacus_row_dx_fp*\l__aTableau_abacus_wd_fp}
          \fp_set:Nn \l__aTableau_yb_fp {\l__aTableau_yl_fp+(\l__aTableau_r_int-\l__aTableau_row_int-0.65)*\l__aTableau_abacus_row_dy_fp*\l__aTableau_abacus_ht_fp}

          \draw[aTableau/clearBoxes]
          (\fp_eval:n{\l__aTableau_xa_fp-0.12*\l_tmpa_fp-\l__aTableau_tick_length_fp}, \fp_eval:n{\l__aTableau_ya_fp-0.12*\l_tmpb_fp})
              --++(\fp_eval:n{(\l__aTableau_cols_int+0.24)*\l_tmpa_fp-\l__aTableau_tick_length_fp},\fp_eval:n{(\l__aTableau_cols_int+0.24)*\l_tmpb_fp})
              --(\fp_eval:n{\l__aTableau_xb_fp+(\l__aTableau_cols_int-0.12)*\l_tmpa_fp-\l__aTableau_tick_length_fp}, \fp_eval:n{\l__aTableau_yb_fp+(\l__aTableau_cols_int+0.12)*\l_tmpb_fp})
              --(\fp_eval:n{\l__aTableau_xb_fp-0.12*\l_tmpa_fp-\l__aTableau_tick_length_fp}, \fp_eval:n{\l__aTableau_yb_fp-0.12*\l_tmpb_fp})
              --cycle
          ;
          % first blank out the possible header line and replace with dots
          \draw[aTableau/dottedLine](\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp);

          % now draw the dots
          \int_step_inline:nnn {2} {\l__aTableau_cols_int}
          {
              \fp_add:Nn \l__aTableau_xa_fp {\l_tmpa_fp}
              \fp_add:Nn \l__aTableau_ya_fp {\l_tmpb_fp}
              \fp_add:Nn \l__aTableau_xb_fp {\l_tmpa_fp}
              \fp_add:Nn \l__aTableau_yb_fp {\l_tmpb_fp}
              \draw[aTableau/dottedLine](\fp_use:N\l__aTableau_xa_fp,\fp_use:N\l__aTableau_ya_fp)--(\fp_use:N\l__aTableau_xb_fp,\fp_use:N\l__aTableau_yb_fp);
          }
    }
}

% ---------------------------------------------------------------------------
% Define keys for the aTableau options and set their defaults

% setting TikZ styles via styles = { ... }
\cs_new_protected:Npn \__aTableau_tikzset:nn #1 #2        { \pgfqkeys{/tikz}{#1/.style={#2}} }

% appending to aTableau styles
\cs_new_protected:Npn \__aTableau_tikzset_append:nn #1 #2 { \pgfqkeys{/tikz/aTableau}{#1/.append~style={#2,}} }

% aTableau options/keys
\keys_define:nn { aTableau }
{
    % ---------------------------------------------------------------------------
    % print an error for non-empty unknown keys
    unknown .code:n = { \tl_if_empty:nF {#1} { \msg_error:nnn {aTableau} {invalid-key} {#1} } },

    % ---------------------------------------------------------------------------
    % general settings

    % picture alignment
    align .choice:,
    align/top    .code:n = { \tikzset{baseline=(current~bounding~box.north)} },
    align/north  .code:n = { \tikzset{baseline=(current~bounding~box.north)} },
    align/center .code:n = { \tikzset{baseline=(current~bounding~box.center)}},
    align/centre .code:n = { \tikzset{baseline=(current~bounding~box.center)}},
    align/bottom .code:n = { \tikzset{baseline=(current~bounding~box.south)} },
    align/south  .code:n = { \tikzset{baseline=(current~bounding~box.south)} },
    align/unknown .code:n = { \msg_error:nnn { aTableau } { unknown-baseline } {#1} },
    align .initial:n     = centre,

    % set the beamer command \only, \uncover, ...
    beamer .choices:nn = { invisible, only, onslide, uncover, visible }{
        \tl_set:Nn \l__aTableau_beamer_tl {#1}
    },
    beamer .unknown .code:n = { \msg_error:nne { aTableau } { unknown-beamer } {#1} },
    beamer .value_required:n = true,
    beamer .initial:n = only,

    % set the Cartan type
    cartan .choices:nn = { A, C, AA, DD }
    {
        % record the Cartan type for use in abacuses
        \tl_set:Nn \l__aTableau_cartan_tl {#1}
        % define all of the type dependent functions here...
        \cs_set_eq:Nc \__aTableau_residue:nn {__aTableau_residue_#1:nn}
    },
    cartan/unknown .code:n = { \msg_error:nne { aTableau } { unknown-cartan } {#1} },
    cartan .initial:n = A,

    charge .code:n =
    {
      % To cater for multipartitions, the charge is a sequence .
      % Set l__aTableau_charge_int is the first item in the sequence
      \regex_split:nnN {[,\|]} {#1} \l__aTableau_charge_seq
      \int_set:Nn \l__aTableau_charge_int { \seq_item:Nn \l__aTableau_charge_seq {1} }
    },
    charge .value_required:n = true,
    charge .initial:n = 0,

    % change the colour theme
    colour~theme .choices:nn = { default, classic, natural }
    {
        \clist_map_inline:nn {Main,Inner,Skew,Fill,Star,Rod}
        {
            \__aTableau_define_html_colour:no {####1} { \prop_item:Nn \l__aTableau_colour_theme_prop {#1_####1} }
        }
    },
    colour~theme/unknown .code:n = { \msg_error:nnx {aTableau} {unknown-theme} {#1} },
    colour~theme .initial:n = default,

    color~theme .meta:n = { colour~theme={#1} },
    color~theme .value_required:n = true,

    % dotted rows and columns for tableaux and abacuses
    dotted~cols .code:n = { \__aTableau_set_multi_seq_key:nn {dotted_cols} {#1} },
    dotted~cols .value_required:n = true,

    dotted~rows .code:n = { \__aTableau_set_multi_seq_key:nn {dotted_rows} {#1} },
    dotted~rows .value_required:n = true,

    e .int_set:N = \l__aTableau_e_int,
    e .initial:n = 1000, % an arbitrary large default

    entries .tl_set:N = \l__aTableau_show_tl,  % automatic bead labelling
    entries .value_required:n = false,
    entries .initial:n = ,

    halign .choice:,
    halign/center .code = {\cs_set_eq:NN \__aTableau_halign:n \hbox_overlap_center:n },
    halign/centre .code = {\cs_set_eq:NN \__aTableau_halign:n \hbox_overlap_center:n },
    halign/left   .code = {\cs_set_eq:NN \__aTableau_halign:n \hbox_overlap_left:n   },
    halign/right  .code = {\cs_set_eq:NN \__aTableau_halign:n \hbox_overlap_right:n  },
    halign/unknown .code:n = { \msg_error:nne { aTableau } { unknown-halign } {#1}   },
    halign .initial:n = centre,

    % math/text mode for boxes and beads
    math~boxes .code:n = { \cs_set_eq:NN \__aTableau_entry:n \__aTableau_entry_math:n },
    text~boxes .code:n = { \cs_set_eq:NN \__aTableau_entry:n \__aTableau_entry_text:n },
    % for compatibility with v2.1.0 we still allow
    math~entries .code:n = { \cs_set_eq:NN \__aTableau_entry:n \__aTableau_entry_math:n },
    text~entries .code:n = { \cs_set_eq:NN \__aTableau_entry:n \__aTableau_entry_text:n },

    % tableau node name prefix
    name .tl_set:N  = \l__aTableau_prefix_tl,
    name .value_required:n = true,
    name .initial:n = A,

    % rotating coordinate systems
    rotate .code:n = {
        \fp_set:Nn \l__aTableau_rotate_fp {#1}
        \cs_set_eq:NN \__aTableau_box:n \__aTableau_rotated_box:n
        \fp_set:Nn \l_tmpa_fp { cosd(#1) } % cos(#1), with #1 in degrees
        \fp_set:Nn \l_tmpb_fp { sind(#1) } % sin(#1), with #1 in degrees
        % rotate the tableau coordinates
        \fp_set:Nn \l__aTableau_xa_fp {\l_tmpa_fp*\l__aTableau_box_col_dx_fp-\l_tmpb_fp*\l__aTableau_box_col_dy_fp}
        \fp_set:Nn \l__aTableau_box_col_dy_fp {\l_tmpb_fp*\l__aTableau_box_col_dx_fp+\l_tmpa_fp*\l__aTableau_box_col_dy_fp}
        \fp_set_eq:NN \l__aTableau_box_col_dx_fp \l__aTableau_xa_fp
        \fp_set:Nn \l__aTableau_xa_fp {\l_tmpa_fp*\l__aTableau_box_row_dx_fp-\l_tmpb_fp*\l__aTableau_box_row_dy_fp}
        \fp_set:Nn \l__aTableau_box_row_dy_fp {\l_tmpb_fp*\l__aTableau_box_row_dx_fp+\l_tmpa_fp*\l__aTableau_box_row_dy_fp}
        \fp_set_eq:NN \l__aTableau_box_row_dx_fp \l__aTableau_xa_fp
        % rotate the abacus coordinates
        \fp_set:Nn \l__aTableau_xa_fp {\l_tmpa_fp*\l__aTableau_abacus_col_dx_fp-\l_tmpb_fp*\l__aTableau_abacus_col_dy_fp}
        \fp_set:Nn \l__aTableau_abacus_col_dy_fp {\l_tmpb_fp*\l__aTableau_abacus_col_dx_fp+\l_tmpa_fp*\l__aTableau_abacus_col_dy_fp}
        \fp_set_eq:NN \l__aTableau_abacus_col_dx \l__aTableau_xa_fp
        \fp_set:Nn \l__aTableau_xa_fp {\l_tmpa_fp*\l__aTableau_abacus_row_dx_fp-\l_tmpb_fp*\l__aTableau_abacus_row_dy_fp}
        \fp_set:Nn \l__aTableau_abacus_row_dy_fp {\l_tmpb_fp*\l__aTableau_abacus_row_dx_fp+\l_tmpa_fp*\l__aTableau_abacus_row_dy_fp}
        \fp_set_eq:NN \l__aTableau_abacus_row_dx \l__aTableau_xa_fp
    },
    rotate .value_required:n = true,

    % scaling
    scale .code:n = { \__aTableau_set_xscale:n {#1} \__aTableau_set_yscale:n {#1} },
    scale .value_required:n = true,

    % subscript and subsubscript scaling
    script .fp_set:N = \l__aTableau_script_fp,
    script .value_required:n = true,
    script .initial:n = 0.5,

    scriptscript .fp_set:N = \l__aTableau_scriptscript_fp,
    scriptscript .value_required:n = true,
    scriptscript .initial:n = 0.4,

    tableau~star .code:n = { \__aTableau_tikzset_append:nn {tableauStar} {#1} },
    tableau~star .value_required:n = true,
    star~style .meta:n = {tableau~star={#1}}, % depreciating

    % shortcut for setting TikZ styles, following a suggestion of Skillmon to use \keyval_parse:nnn
    styles .code:n = { \keyval_parse:nnn { \msg_error:nnn {aTableau}{missing-style} } { \__aTableau_tikzset:nn } { #1 } },

    % tikzpicture environment
    tikzpicture .tl_set:N= \l__aTableau_tikzpicture_tl,
    tikzpicture .value_required:n = true,
    tikzpicture .initial:n =,

    % tikz code after
    tikz~after .tl_set:N = \l__aTableau_tikz_after_tl,
    tikz~after .value_required:n = true,
    tikz~after .initial:n = ,

    % tikz~ code before
    tikz~before .tl_set:N = \l__aTableau_tikz_before_tl,
    tikz~before .value_required:n = true,
    tikz~before .initial:n = ,

    valign .choices:nn = { bottom, center, centre, top } { \cs_set_eq:Nc \__aTableau_valign:n { __aTableau_valign_#1:n } },
    valign/unknown .code:n = { \msg_error:nne { aTableau } { unknown-valign } {#1} },
    valign .initial:n = centre,

    xscale .code:n = { \__aTableau_set_xscale:n {#1} },
    xscale .value_required:n = true,
    xscale .initial:n =1,

    yscale .code:n = { \__aTableau_set_yscale:n {#1} },
    yscale .value_required:n = true,
    yscale .initial:n =1,

    % ---------------------------------------------------------------------------
    % tableau settings

    % convention switches
    Australian .code:n = \__aTableau_set_tableau_style_australian:,
    australian .code:n = \__aTableau_set_tableau_style_australian:,
    australian .value_required:n = false,

    English .code:n = \__aTableau_set_tableau_style_english:,
    english .code:n = \__aTableau_set_tableau_style_english:,
    english .value_required:n = false,
    english .initial:n =, % default style

    French .code:n = \__aTableau_set_tableau_style_french:,
    french .code:n = \__aTableau_set_tableau_style_french:,
    french .value_required:n = false,

    ukrainian .code:n = \__aTableau_set_tableau_style_ukrainian:,
    Ukrainian .code:n = \__aTableau_set_tableau_style_ukrainian:,
    ukrainian .value_required:n = false,

    Russian .code:n = \__aTableau_set_tableau_style_ukrainian:,
    russian .code:n = \__aTableau_set_tableau_style_ukrainian:,

    border .bool_set:N = \l__aTableau_border_bool,
    border .default:n = true,
    border .initial:n = true,

    no~border .bool_set_inverse:N = \l__aTableau_border_bool,
    no~border .default:n = true,

    % border colours

    border~color .tl_set:N = \l__aTableau_outer_tl,  % an alias
    border~color .value_required:n = true,
    border~colour .tl_set:N = \l__aTableau_outer_tl,
    border~colour .value_required:n = true,
    border~colour .initial:n = aTableauMain,

    border~style .code:n = { \__aTableau_tikzset_append:nn {border} {#1} },
    border~style .value_required:n = true,

    % node height and width
    box~height .code:n = {
        \fp_set:Nn \l__aTableau_box_col_dy_fp { \l__aTableau_box_col_dy_fp*#1/\l__aTableau_box_ht_fp}
        \fp_set:Nn \l__aTableau_box_row_dy_fp { \l__aTableau_box_row_dy_fp*#1/\l__aTableau_box_ht_fp}
        \fp_set:Nn \l__aTableau_box_ht_fp {#1}
    },
    box~height .value_required:n = true,
    box~height .initial:n = 0.5,

    box~width .code:n = {
        \fp_set:Nn \l__aTableau_box_col_dx_fp { \l__aTableau_box_col_dx_fp*#1/\l__aTableau_box_wd_fp}
        \fp_set:Nn \l__aTableau_box_row_dx_fp { \l__aTableau_box_row_dx_fp*#1/\l__aTableau_box_wd_fp}
        \fp_set:Nn \l__aTableau_box_wd_fp {#1}
    },
    box~width .value_required:n = true,
    box~width .initial:n = 0.5,

    % box styling
    box~fill .tl_set:N = \l__aTableau_box_fill_tl,
    box~fill .value_required:n = true,
    box~fill .initial:n = white,

    box~font .tl_set:N = \l__aTableau_box_font_tl,
    box~font .value_required:n = true,
    box~font .initial:n =,

    box~shape .tl_set:N = \l__aTableau_box_shape_tl,
    box~shape .value_required:n = true,
    box~shape .initial:n = rectangle,

    box~text .tl_set:N = \l__aTableau_box_text_tl,
    box~text .value_required:n = true,
    box~text .initial:n = aTableauMain,

    box~style .code:n = { \__aTableau_tikzset_append:nn {tableauBox} {#1} },
    box~style .value_required:n = true,

    boxes .bool_set:N = \l__aTableau_boxes_bool,
    boxes .default:n = true,
    boxes .initial:n = true,

    no~boxes .bool_set_inverse:N = \l__aTableau_boxes_bool,
    no~boxes .default:n = true,

    colours .code:n = \__aTableau_set_colours:n {#1},
    colors  .code:n = \__aTableau_set_colours:n {#1},
    colours .value_required:n = true,
    colors  .value_required:n = true,

    conjugate .code:n =
    {   % change to using conjugate box coordinates
        \tl_set:Nx \l_tmpa_tl {\str_lowercase:n {#1}}
        \str_if_eq:VnTF \l_tmpa_tl {true}
        {
            \bool_set_true:N \l__aTableau_conjugate_bool
            \cs_set_eq:NN \__aTableau_set_box_coordinates:nnn \__aTableau_set_box_coordinates_conjugate:nnn
        }
        {
            \bool_set_false:N \l__aTableau_conjugate_bool
            \cs_set_eq:NN \__aTableau_set_box_coordinates:nnn \__aTableau_set_box_coordinates_normal:nnn
        }
    },
    conjugate .default:n = true,
    conjugate .initial:n = false,

    inner~wall .tl_set:N = \l__aTableau_inner_tl,
    inner~wall .value_required:n = true,
    inner~wall .initial:n = aTableauInner,

    inner~style .code:n = { \__aTableau_tikzset_append:nn {innerWall} {#1} },
    inner~style .value_required:n = true,

    % label
    label .code:n = { \__aTableau_set_multi_tl_key:nn {label} {#1} },

    label~style .code:n = { \__aTableau_tikzset_append:nn {label} {#1} },
    label~style .value_required:n = true,

    % -- paths ---------------------------------------
    paths .code:n = { \__aTableau_set_multi_tl_key:nn {paths} {#1} },
    paths .initial:n = ,

    path~style .code:n = { \__aTableau_tikzset_append:nn {path} {#1} },
    path~style .value_required:n = true,
    path~style .initial:n =,

    path~box .tl_set:N = \l__aTableau_path_box_tl,
    path~box .initial:n = ,

    path~box~style .code:n = { \__aTableau_tikzset_append:nn {pathBox} {#1} },
    path~box~style .value_required:n = true,
    path~box~style .initial:n =,

    % -- ribbons ---------------------------------------
    ribbons .code:n = { \__aTableau_set_multi_tl_key:nn {ribbons} {#1} },
    ribbons .initial:n = ,

    ribbon~style .code:n = { \__aTableau_tikzset_append:nn {ribbon} {#1} },
    ribbon~style .value_required:n = true,
    ribbon~style .initial:n =,

    ribbon~box .tl_set:N = \l__aTableau_ribbon_box_tl,
    ribbon~box .initial:n = ,

    ribbon~box~style .code:n = { \__aTableau_tikzset_append:nn {ribbonBox} {#1} },
    ribbon~box~style .value_required:n = true,
    ribbon~box~style .initial:n =,

    shape .code:n = { \__aTableau_set_partition:nn {shape} {#1} },

    % -- snobs ---------------------------------------
    snobs .code:n = { \__aTableau_set_multi_tl_key:nn {snobs} {#1} },
    snobs .initial:n = ,

    snob~style .code:n = { \__aTableau_tikzset_append:nn {snob} {#1} },
    snob~style .value_required:n = true,
    snob~style .initial:n =,

    snob~box .tl_set:N = \l__aTableau_snob_box_tl,
    snob~box .initial:n = ,

    snob~box~style .code:n = { \__aTableau_tikzset_append:nn {snobBox} {#1} },
    snob~box~style .value_required:n = true,
    snob~box~style .initial:n =,

    % -- shifted ------------
    shifted .bool_set:N = \l__aTableau_shifted_bool,
    shifted .initial:n  = false,

    % -- cover settings ------------
    cover .code:n = \__aTableau_set_multi_shape_key:nn {cover} {#1},
    cover .value_required:n = true,
    cover .initial:n =,

    cover~border .bool_set:N = \l__aTableau_cover_border_bool,
    cover~border .default:n = true,
    cover~border .initial:n  = true,

    no~cover~border .bool_set_inverse:N = \l__aTableau_cover_border_bool,
    no~cover~border .default:n = true,

    cover~border~style .code:n = { \__aTableau_tikzset_append:nn {coverBorder} {#1} },
    cover~border~style .value_required:n = true,

    cover~boxes .bool_set:N = \l__aTableau_cover_boxes_bool,
    cover~boxes .default:n  = true,
    cover~boxes .initial:n  = true,

    no~cover~boxes .bool_set_inverse:N = \l__aTableau_cover_boxes_bool,

    cover~box~style .code:n = { \__aTableau_tikzset_append:nn {coverBox} {#1} },
    cover~box~style .value_required:n = true,

    cover colour .tl_set:N = \l__aTableau_cover_border_tl,
    cover color .tl_set:N = \l__aTableau_cover_border_tl,
    cover colour .initial:n = aTableauSkew,

    % -- skew settings ------------
    skew .code:n = \__aTableau_set_multi_shape_key:nn {skew} {#1},
    skew .value_required:n = true,
    skew .initial:n = 0,

    skew~border .bool_set:N = \l__aTableau_skew_border_bool,
    skew~border .initial:n  = false,
    skew~border .default:n  = true,

    no~skew~border .bool_set_inverse:N = \l__aTableau_skew_border_bool,
    no~skew~border .default:n = true,

    skew~border~style .code:n = { \__aTableau_tikzset_append:nn {skewBorder} {#1} },
    skew~border~style .value_required:n = true,

    skew~boxes .bool_set:N = \l__aTableau_skew_boxes_bool,
    skew~boxes .default:n  = true,
    skew~boxes .initial:n  = false,

    no~skew~boxes .bool_set_inverse:N = \l__aTableau_skew_boxes_bool,

    skew~box~style .code:n = { \__aTableau_tikzset_append:nn {skewBox} {#1} },
    skew~box~style .value_required:n = true,

    skew colour .tl_set:N = \l__aTableau_skew_border_tl,
    skew color .tl_set:N = \l__aTableau_skew_border_tl,
    skew colour .initial:n = aTableauSkew,

    % -- tabloid shapes ------------
    tabloid .bool_set:N = \l__aTableau_tabloid_bool,
    tabloid .initial:n  = false,

    % -- multitableaux and multidiagrams --------------------

    delimiters .code:n = { \__aTableau_set_delimiters:nn #1 },
    delimiters .value_required:n = true,
    delimiters .initial:n = (),

    left~delimiter .tl_set:N = \l__aTableau_left_delimiter_tl,
    left~delimiter .value_required:n = true,

    right~delimiter .tl_set:N = \l__aTableau_right_delimiter_tl,
    right~delimiter .value_required:n = true,

    empty .tl_set:N = \l__aTableau_empty_tl,
    empty .initial:n = -,

    separators .bool_set:N = \l__aTableau_separators_bool,
    separators .default:n = true,
    separators .initial:n = true,

    no~separators .bool_set_inverse:N = \l__aTableau_separators_bool,
    no~separators .default:n = true,

    separation .fp_set:N  = \l__aTableau_separation_fp,
    separation .value_required:n = true,
    separation .initial:n = 0.3,

    separator .tl_set:N = \l__aTableau_separator_tl,
    separator .value_required:n = true,
    separator .initial:n = |,

    separator~colour .tl_set:N  = \l__aTableau_separator_fg_tl,
    separator~colour .value_required:n = true,
    separator~colour .initial:n = aTableauMain,

    separator~color .tl_set:N  = \l__aTableau_separator_fg_tl,
    separator~color .value_required:n = true,

    % set rows in abacuses, multitableau and multidiagrams
    rows .code:n = {
      \fp_set:Nn  \l__aTableau_rows_fp  {#1}
      \int_set:No \l__aTableau_rows_int {\fp_to_int:N  \l__aTableau_rows_fp}
    },
    rows .value_required:n = true,
    rows .initial:n = 0,

    xoffsets .code:n = { \regex_split:nnN {[\|,]} {#1} \l__aTableau_xoffsets_seq },
    xoffsets .value_required:n = true,
    xoffsets .initial:n = 0,

    yoffsets .code:n = { \regex_split:nnN {[\|,]} {#1} \l__aTableau_yoffsets_seq },
    yoffsets .value_required:n = true,
    yoffsets .initial:n = 0,

    % ---------------------------------------------------------------------------
    % abacus keys

    south .code:n = \__aTableau_set_abacus_style:nnnn {1}{0}{0}{-1},
    south .value_required:n = false,
    south .initial:n =, % use south by default

    east  .code:n = \__aTableau_set_abacus_style:nnnn {0}{-1}{1}{0},
    east .value_required:n = false,

    north .code:n = \__aTableau_set_abacus_style:nnnn {1}{0}{0}{1},
    north .value_required:n = false,

    west  .code:n = \__aTableau_set_abacus_style:nnnn {0}{1}{-1}{0},
    west .value_required:n = false,

    % abacus style
    abacus~ends .code:n = { \__aTableau_set_abacus_ends:nn #1 },
    abacus~ends .value_required:n = true,
    abacus~ends .initial:n = {-|},

    abacus~ends~style .code:n = { \__aTableau_tikzset_append:nn {abacusEnds} {#1} },
    abacus~ends~style .value_required:n = true,

    abacus~bottom .fp_set:N = \l__aTableau_abacus_bottom_fp,
    abacus~bottom .value_required:n = true,
    abacus~bottom .initial:n = 0.1,

    abacus~top .code:n = \fp_set:Nn \l__aTableau_abacus_top_fp {1-#1},
    abacus~top .value_required:n = true,
    abacus~top .initial:n = 0.4,

    abacus~star .code:n = { \__aTableau_tikzset_append:nn {abacusStar} {#1} },
    abacus~star .value_required:n = true,
    abacus~star~style .meta:n = {abacus~star={#1}}, % depreciating

    bead .tl_set:N = \l__aTableau_bead_tl, % bead colour
    bead .value_required:n = true,
    bead .initial:n = aTableauMain,

    bead~font .tl_set:N = \l__aTableau_bead_font_tl, % bead font
    bead~font .initial:n = \small,
    bead~font .value_required:n = true,

    bead~height .fp_set:N = \l__aTableau_bead_height_fp,
    bead~height .value_required:n = true,
    bead~height .initial:n = 0.4,

    bead~sep .fp_set:N = \l__aTableau_abacus_ht_fp, % bead separation
    bead~sep .value_required:n = true,
    bead~sep .initial:n = 0.42,

    bead~shape .tl_set:N = \l__aTableau_bead_shape_tl, % bead shape colour
    bead~shape .value_required:n = true,
    bead~shape .initial:n = circle,

    bead~size .code:n = {
        \fp_set:Nn \l__aTableau_bead_height_fp {#1}
        \fp_set:Nn \l__aTableau_bead_width_fp {#1}
    },
    bead~size .value_required:n = true,

    bead~style .code:n = { \__aTableau_tikzset_append:nn {abacusBead} {#1} },
    bead~style .value_required:n = true,

    bead~text .tl_set:N = \l__aTableau_bead_text_tl, % bead text colour
    bead~text .value_required:n = true,
    bead~text .initial:n = white,

    bead~width .fp_set:N = \l__aTableau_bead_width_fp,
    bead~width .value_required:n = true,
    bead~width .initial:n = 0.4,

    beads .int_set:N = \l__aTableau_beads_int, % number of beads on the abacus
    beads .initial:n = 0,
    beads.value_required:n = true,

    beam~height .fp_set:N = \l__aTableau_beam_height_fp, % beam height
    beam~height .value_required:n = true,
    beam~height .initial:n = 0.13,

    beta~numbers .bool_set:N = \l__aTableau_beta_numbers_bool,
    beta~numbers .default:n = true,
    beta~numbers .initial:n = false,

    framed .bool_set:N = \l__aTableau_framed_bool,
    framed .default:n = true,
    framed .initial:n = false,

    row~labels .code:n = { \seq_set_split:Nnn \l__aTableau_row_labels_seq {,} {#1} },
    row~labels .value_required:n = true,

    row~label~style .code:n = { \__aTableau_tikzset_append:nn {rowLabel} {#1} },
    row~label~style .value_required:n = true,

    runner .tl_set:N = \l__aTableau_runner_tl, % runner colour
    runner .value_required:n = true,

    runner~style .code:n = { \__aTableau_tikzset_append:nn {abacusRunner} {#1} },
    runner~style .value_required:n = true,

    runner~labels .code:n = { \seq_set_split:Nnn \l__aTableau_runner_labels_seq {,} {#1} },
    runner~labels .value_required:n = true,

    runner~label~style .code:n = { \__aTableau_tikzset_append:nn {runnerLabel} {#1} },
    runner~label~style .value_required:n = true,

    runner~sep .fp_set:N = \l__aTableau_abacus_wd_fp, % runner separation
    runner~sep .value_required:n = true,
    runner~sep .initial:n = 0.42,

    shading .tl_set:N = \l__aTableau_shading_tl,
    shading .value_required:n = true,
    shading .initial:n = ball,

    tick .tl_set:N = \l__aTableau_tick_tl, % tick colour
    tick .initial:n = aTableauInner,

    tick~length .code:n = { \fp_set:Nn \l__aTableau_tick_length_fp {#1/2} }, % (half) tick width separation
    tick~length .value_required:n = true,
    tick~length .initial:n = 0.1,

    tick~style .code:n = { \__aTableau_tikzset_append:nn {abacusTick} {#1} },
    tick~style .value_required:n = true,

    traditional .code:n =
    {
        \bool_if:cTF {c_#1_bool}
        { \__aTableau_set_abacus_convention_traditional: }
        { \__aTableau_set_abacus_convention_standard:    }
    },
    traditional .value_required:n = false,
    traditional .default:n = true,
    traditional .initial:n = false,

    unshaded .code:n = { \__aTableau_tikzset_append:nn {abacusBead} {unshaded} },
    unshaded .value_required:n = false,
}

% ---------------------------------------------------------------------------
% Usage: \__aTableau_set_origin:nn (x,y)
% Set the Cartesian coordinates for the corner of the (1,1) box
% TODO: allow general TikZ-coordinates. To do this we could, for example, use ideas from
% https://tex.stackexchange.com/questions/33703/extract-x-y-coordinate-of-an-arbitrary-point-in-tikz
\cs_new_protected:Npn \__aTableau_set_origin:nn (#1,#2)
{
    \fp_set:Nn \l__aTableau_x_fp {#1}
    \fp_set:Nn \l__aTableau_y_fp {#2}
}

% usage: \__aTableau_tikzpicture:nnn {origin} {aTableau command}
%  - #1: origin are Cartesian coordinates in the form x,y, or NoValue
%  - #2: an internal aTableau command with parameters for drawing something
% Except for \aTabset and \NewATableauCommand, all public-facing commands
% use \__aTableau_tikzpicture:nnn, which ensures that the command is
% run inside a tikzpicture environment
\cs_new_protected:Npn \__aTableau_tikzpicture:nn #1 #2
{
      \IfNoValueTF {#1}
      {   % Wrap inside a tikzpicture environment, placing the picture at (0,0).
          % Checking for the existence of the \draw command is a proxy for
          % determining whether we are inside a tikzpicture environment since
          % it is only defined when we are
          \if_cs_exist:N \draw \msg_error:nn {aTableau} { inside-tikzpicture} \fi:

          \__aTableau_set_origin:nn (0,0)
          \mode_if_math:TF
          {
              % We want to automatically rescale when used as a subscript, which
              % we do by putting the tikz code inside a box and then using
              % \mathchoice to adjust for script size, using an idea of cfr's

              \hbox_set:Nw \l_tmpa_box
                  \exp_last_unbraced:Ne \tikz{[\l__aTableau_tikzpicture_tl]}
                    { \l__aTableau_tikz_before_tl #2 \l__aTableau_tikz_after_tl }
              \hbox_set_end:

              \mathchoice
              {   % display style: do nothing
                  \box_use:N \l_tmpa_box
              }
              {   % text style: do nothing
                  \box_use:N \l_tmpa_box
              }
              {   % script style => rescale using \l__aTableau_script_fp
                  \box_scale:Nnn \l_tmpa_box {\l__aTableau_script_fp*\l__aTableau_xscale_fp}
                                             {\l__aTableau_script_fp*\l__aTableau_yscale_fp}
                  \box_use:N \l_tmpa_box
              }
              {   % scriptscript style => rescale using \l__aTableau_scriptscript_fp
                  \box_scale:Nnn \l_tmpa_box {\l__aTableau_scriptscript_fp*\l__aTableau_xscale_fp}
                                             {\l__aTableau_scriptscript_fp*\l__aTableau_yscale_fp}
                  \box_use:N \l_tmpa_box
              }
          }
          {   % Not in maths-mode. In the manual, tcolorbox objects to using a box here
              \exp_last_unbraced:Ne \tikz{[\l__aTableau_tikzpicture_tl]}
                { \l__aTableau_tikz_before_tl #2 \l__aTableau_tikz_after_tl }
          }
      }
      {   % already inside a tikzpicture environment
          \if_cs_exist:N \draw \else: \msg_error:nn {aTableau} { outside-tikzpicture} \fi:
          \__aTableau_set_origin:nn (#1)
          \l__aTableau_tikz_before_tl #2 \l__aTableau_tikz_after_tl
      }
}

% The next two wrapper commands apply the settings inside a group, to keep
% changes local.  They then call \__aTableau_tikzpicture:nn to ensure that the
% aTableau command is run inside a tikzpicture environment. We do it this way
% so that the commands are beamer enabled only if beamer is being used. We call
% the settings below so that the aTableau settings can change the beamer
% overlay command that is used.

% usage: \__aTableau_command_wrapper:nnn {settings} {origin} {aTableau command}
\cs_new_protected:Npn \__aTableau_command_wrapper:nnn #1 #2 #3
{
    % keep changes to settings local by working inside a group
    \group_begin:
        % keep changes to settings local by working inside a group
        \keys_set:nn {aTableau} {#1}
        \__aTableau_tikzpicture:nn {#2} {#3}
    \group_end:
}

% usage: \__aTableau_beamer_wrapper:nnnn {overlay specification} {settings} {origin} {aTableau command}
\cs_new_protected:Npn \__aTableau_beamer_wrapper:nnnn #1 #2 #3 #4
{
    % Keep changes to settings local by working inside a group
    % Process the keys before invoking \__aTableau_tikzpicture:nn so
    % that the beamer key can change the beamer overlay command
    \group_begin:
        \keys_set:nn {aTableau} {#2}
        \IfNoValueTF {#1}
        {
            \__aTableau_tikzpicture:nn {#3} {#4}
        }
        {   % apply beamer overlay command
            \cs:w \l__aTableau_beamer_tl \cs_end: <#1> { \__aTableau_tikzpicture:nn {#3} {#4} }
        }
    \group_end:
}

% ---------------------------------------------------------------------------
% Public facing aTableau commands

% usage: \NewATableauCommand \Commmand {aTableau command name} {keys}
% Define custom aTableau commands.  We could shorten the definition of this
% command by having only one \IfClassLoadedTF at the top, but it is better to
% keep the normal and beamer versions of each command close together for easier
% comparison.
\NewDocumentCommand\NewATableauCommand{ mmm }
{
    \str_case:nnF {#2}
    {
        {Abacus}
          {
            \IfClassLoadedTF {beamer}
            {
                % \AbacusCommand <overlay specs> (x,y) [style] {#runners} {partition}
                \NewDocumentCommand#1{ d<> d() O{} m m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {#3,##3} {##2} { \__aTableau_abacus:nn {##4} {##5} }
                }
            }
            {
                % \AbacusCommand (x,y) [style] {#runners} {partition}
                \NewDocumentCommand#1{ d() O{} m m }
                {
                    \__aTableau_command_wrapper:nnn {#3,##2} {##1} { \__aTableau_abacus:nn {##3} {##4} }
                }
            }
          }
        {Diagram}
          {
            \IfClassLoadedTF {beamer}
            {
                % \DiagramCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {#3,##3} {##2} { \__aTableau_draw_diagram:n {##4} }
                }
            }
            {
                % \DiagramCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {#3,##2} {##1} { \__aTableau_draw_diagram:n {##3} }
                }
            }
          }
        {Multidiagram}
          {
            \IfClassLoadedTF {beamer}
            {
                % \MultidiagramCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {#3,##3} {##2} { \__aTableau_multidiagram:n {##4} }
                }
            }
            {
                % \MultidiagramCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {#3,##2} {##1} { \__aTableau_multidiagram:n {##3} }
                }
            }
          }
        {Multitableau}
          {
            \IfClassLoadedTF {beamer}
            {
                % \MultitableauCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {#3,##3} {##2} { \__aTableau_multitableau:n {##4} }
                }
            }
            {
                % \MultitableauCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {#3,##2} {##1} { \__aTableau_multitableau:n {##3} }
                }
            }
          }
        {RibbonTableau}
          {
            \IfClassLoadedTF {beamer}
            {
                % \RibbonTableauCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {#3,##3} {##2} { \__aTableau_ribbon_tableau:n {##4} }
                }
            }
            {
                % \RibbonTableauCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {#3,##2} {##1} { \__aTableau_ribbon_tableau:n {##3} }
                }
            }
          }
        {ShiftedDiagram}
          {
            \IfClassLoadedTF {beamer}
            {
                % \ShiftedDiagramCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {shifted,#3,##3} {##2} { \__aTableau_draw_diagram:n {##4} }
                }
            }
            {
                % \ShiftedDiagramCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {shifted,#3,##2} {##1} { \__aTableau_draw_diagram:n {##3} }
                }
            }
          }
        {ShiftedTableau}
          {
            \IfClassLoadedTF {beamer}
            {
                % \ShiftedTableauCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {shifted,#3,##3} {##2} { \__aTableau_draw_tableau:n {##4} }
                }
            }
            {
                % \ShiftedTableauCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {shifted,#3,##2} {##1} { \__aTableau_draw_tableau:n {##3} }
                }
            }
          }
        {SkewDiagram}
          {
            \IfClassLoadedTF {beamer}
            {
                % \SkewDiagramCommand <overlay specs> (x,y) [style] {skew shape} {entries}
                \NewDocumentCommand#1{ d<> d() O{} m m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {skew={##4},#3,##3} {##2} { \__aTableau_draw_diagram:n {##5} }
                }
            }
            {
                % \SkewDiagramCommand (x,y) [style] {skew shape} {entries}
                \NewDocumentCommand#1{ d() O{} m m }
                {
                    \__aTableau_command_wrapper:nnn {skew={##3},#3,##2} {##1} { \__aTableau_draw_diagram:n {##4} }
                }
            }
          }
        {SkewTableau}
          {
            \IfClassLoadedTF {beamer}
            {
                % \SkewTableauCommand <overlay specs> (x,y) [style] {skew shape} {entries}
                \NewDocumentCommand#1{ d<> d() O{} m m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {skew={##4},#3,##3} {##2} { \__aTableau_draw_tableau:n {##5} }
                }
            }
            {
                % \SkewTableauCommand (x,y) [style] {skew shape} {entries}
                \NewDocumentCommand#1{ d() O{} m m }
                {
                    \__aTableau_command_wrapper:nnn {skew={##3},#3,##2} {##1} { \__aTableau_draw_tableau:n {##4} }
                }
            }
          }
        {Tableau}
          {
            \IfClassLoadedTF {beamer}
            {
                % \TableauCommandCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {#3,##3} {##2} { \__aTableau_draw_tableau:n {##4} }
                }
            }
            {
                % \TableauCommandCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {#3,##2} {##1} { \__aTableau_draw_tableau:n {##3} }
                }
            }
          }
        {Tabloid}
          {
            \IfClassLoadedTF {beamer}
            {
                % \TabloidCommand <overlay specs> (x,y) [style] {entries}
                \NewDocumentCommand#1{ d<> d() O{} m }
                {
                    \__aTableau_beamer_wrapper:nnnn {##1} {tabloid,#3,##3} {##2} { \__aTableau_draw_tableau:n {##4} }
                }
            }
            {
                % \TabloidCommand (x,y) [style] {entries}
                \NewDocumentCommand#1{ d() O{} m }
                {
                    \__aTableau_command_wrapper:nnn {tabloid,#3,##2} {##1} { \__aTableau_draw_tableau:n {##3} }
                }
            }
          }
    }
    {
       \msg_error:nnx {aTableau} {unrecognised-aTableau-command} {#2}
    }
}

% Use \NewATableauCommand command to define the aTableau commands
\NewATableauCommand\Abacus{Abacus}{}
\NewATableauCommand\Diagram{Diagram}{}
\NewATableauCommand\Multidiagram{Multidiagram}{}
\NewATableauCommand\Multitableau{Multitableau}{}
\NewATableauCommand\RibbonTableau{RibbonTableau}{}
\NewATableauCommand\ShiftedDiagram{ShiftedDiagram}{}
\NewATableauCommand\ShiftedTableau{ShiftedTableau}{}
\NewATableauCommand\SkewDiagram{SkewDiagram}{}
\NewATableauCommand\SkewTableau{SkewTableau}{}
\NewATableauCommand\Tableau{Tableau}{}
\NewATableauCommand\Tabloid{Tabloid}{}

% outfacing command to access aTableau keys/options
\NewDocumentCommand\aTabset{ m }{ \keys_set:nn { aTableau } {#1} }

% ---------------------------------------------------------------------------
% Finally, now that everything is defined, process the package options.

\ProcessKeyOptions[ aTableau ]

\endinput

% ---------------------------------------------------------------------------
%
% Copyright (C) 2022-25 by Andrew Mathas <andrew.mathas@gmail.com>
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License (LPPL), either
% version 1.3c of this license or (at your option) any later
% version.  The latest version of this license is in the file:
%
% http://www.latex-project.org/lppl.txt
%
% This work is "maintained" (as per LPPL maintenance status) by
% Andrew Mathas.
%
% This package consists of the files:
%       atableau.ini
%       atableau.pdf
%       atableau.sty
%       atableau.tex
%       atableau_beamer.pdf
%       atableau_beamer.tex
%       atableau_readme.webp
%       LICENSE
%       README.md
%       CHANGES.md
%       TODO.md
%
% ---------------------------------------------------------------------------

% end of atableau.sty
