\RequirePackage{xparse}

\ProvidesExplPackage {returntogrid} {2018/08/21} {0.2}
 {return to a grid position}
% \debug_on:n { check-expressions,deprecation } % needs enable-debug
\RequirePackage{eso-pic}
\RequirePackage{zref-savepos,zref-abspage}

\msg_new:nnn {ufgrid} {tikz-needed}        { the~showdebugpagegrid~command~needs~the~tikz~package }
\msg_new:nnn {ufgrid} {tab-list-unknown}   { the~tab~list~"#1"~has~not~been~declared}

\sys_if_engine_luatex:T
{
 \directlua{require ("returntogrid.lua")}
}

\int_new:N       \l__ufgrid_vskip_int %vskip in sp
\int_new:N       \g__ufgrid_vpoint_int %for the v-labels
\int_new:N       \g__ufgrid_hpoint_int   %for the h-labels of tabs
\int_new:N       \l__ufgrid_tempa_int
\int_new:N       \l__ufgrid_tab_cor_int

\bool_new:N      \g__ufgrid_active_bool
\bool_gset_true:N \g__ufgrid_active_bool
\bool_new:N      \l__ufgrid_hmode_bool   %hmode (tabs)
\bool_new:N      \l__ufgrid_savepos_bool %save position
\bool_new:N      \l__ufgrid_insert_vskip_bool %insert vskip
\bool_new:N      \l__ufgrid_autolabel_bool %numbered labels
\bool_new:N      \l__ufgrid_tabfound_bool
\bool_new:N      \l__ufgrid_hfill_bool
\bool_new:N      \l__ufgrid_debug_tab_bool
\bool_new:N      \g__ufgrid_twoside_bool

\if@twoside \bool_gset_true:N \g__ufgrid_twoside_bool \fi

\tl_new:N        \l__ufgrid_setup_step_tl   %step value (dim in sp)
\tl_new:N        \l__ufgrid_setup_reference_tl %name of reference point
\tl_new:N        \l__ufgrid_setup_offset_tl %an offset, e.g. topskip
\tl_new:N        \l__ufgrid_label_tl        %holds the label name
\tl_new:N        \l__ufgrid_curpos_tl
\tl_new:N        \g__ufgrid_prefix_vpoint_tl %prefix of  v-point labels
\tl_new:N        \g__ufgrid_prefix_tabpos_tl %prefix for tab positions
\tl_new:N        \g__ufgrid_prefix_hpoint_tl %prefix for h-point labels
\tl_new:N        \g__ufgrid_prefix_user_tl   %prefix for labels provided by the user
\tl_new:N        \l__ufgrid_tab_list_tl     %holds the name of the current tab list
\tl_new:N        \l__ufgrid_debug_tab_draw_tl %hold the draw command to debug tab positions
\tl_new:N        \l__ufgrid_tmp_vskip_tl     % to pass the vskip to zsavepos

\dim_new:N       \l__ufgrid_debug_tab_dim

\prop_new:N      \g__ufgrid_vskip_prop %will hold the vskips by label name

\cs_generate_variant:Nn \prop_gput:Nnn {Nxx}
\cs_generate_variant:Nn \prop_get:NnNTF {NxNTF}
\cs_generate_variant:Nn \prop_get:NnNF {NxNF}

% we need to store the current displacement
\zref@newprop {ufgridvskip}[0]{\int_use:N \l__ufgrid_vskip_int}
\zref@newprop*{pagecnt} [1] {\the\c@page}
\zref@addprop {savepos} {ufgridvskip}
\zref@addprop {savepos} {abspage}
\zref@addprop {savepos} {pagecnt}


\keys_define:nn { ufgrid / setup}
{
 activate         .bool_gset:N = \g__ufgrid_active_bool,
 % step = dimension
 step             .code:n   = { \tl_set:Nx \l__ufgrid_setup_step_tl { \dim_to_decimal_in_sp:n {#1} } },
 % reference = labelname (label should be set with zsavepos)
 reference        .tl_set:N  = \l__ufgrid_setup_reference_tl,
 % offset = dimension: move down (example offset= \topskip)
 offset           .code:n    = { \tl_set:Nx \l__ufgrid_setup_offset_tl { \dim_to_decimal_in_sp:n {#1} } },
 tab-list         .tl_set:N  = \l__ufgrid_tab_list_tl,
 settabpositions  .code:n    = { \__ufgrid_settabpositions:nn #1 },
 prefix-vpoint    .tl_gset:N = \g__ufgrid_prefix_vpoint_tl,
 prefix-tabpos    .tl_gset:N = \g__ufgrid_prefix_tabpos_tl,
 prefix-hpoint    .tl_gset:N = \g__ufgrid_prefix_hpoint_tl,
 prefix-user      .tl_gset:N = \g__ufgrid_prefix_user_tl,
 debug-tab        .bool_set:N= \l__ufgrid_debug_tab_bool,
 debug-tab-len    .dim_set:N = \l__ufgrid_debug_tab_dim,
 debug-tab-draw   .tl_set:N  = \l__ufgrid_debug_tab_draw_tl,
 hmode            .bool_set:N= \l__ufgrid_hmode_bool,
 hfill            .bool_set:N= \l__ufgrid_hfill_bool,
 tab              .code:n    =
   {
    \tl_if_empty:nF { #1 }
    {
     \tl_set:Nn \l__ufgrid_tab_list_tl {#1}
    }
    \bool_set_true:N \l__ufgrid_hmode_bool
   },
 twoside          .bool_gset:N = \g__ufgrid_twoside_bool
}

\normalsize %just in case
\keys_set:nn { ufgrid / setup }
 {
  prefix-vpoint = ufgrid@vpoint,
  prefix-tabpos = ufgrid@tabpos,
  prefix-hpoint = ufgrid@hpoint,
  prefix-user   = ufgrid@user,
  step          = \baselineskip,
  reference     = \g__ufgrid_prefix_vpoint_tl textupperleft,
  offset        = \topskip,
  debug-tab-len = -4cm,
  debug-tab-draw= { \draw[blue,dashed] (0,1em)--++(0,\l__ufgrid_debug_tab_dim) },
  hfill
 }


\keys_define:nn { ufgrid / use }
 {
  __savepos      .bool_set:N  =  \l__ufgrid_savepos_bool,
  __insert_vskip .bool_set:N  =  \l__ufgrid_insert_vskip_bool,
  __label        .tl_set:N    =  \l__ufgrid_label_tl,
  __autolabel    .bool_set:N  =  \l__ufgrid_autolabel_bool,
  save           .meta:n      = { __label=\g__ufgrid_prefix_user_tl#1, __savepos=true, __insert_vskip=false,__autolabel=false },
  save           .value_required:n = true,
  label          .meta:n      = { __label=\g__ufgrid_prefix_user_tl#1, __autolabel=false },
  label          .value_required:n = true,
  use            .meta:n      =
    {
     __label=\g__ufgrid_prefix_user_tl#1, __savepos=false, __insert_vskip=true,__autolabel=false
     },
  use            .value_required:n = true,
  strut          .code:n          = \__ufgrid_insert_ufgridstrut:n { #1 },
  hmode          .bool_set:N      = \l__ufgrid_hmode_bool,
  tab-list       .tl_set:N        = \l__ufgrid_tab_list_tl,
  tab            .code:n          =
   {
    \tl_if_empty:nF { #1 }
    {
     \tl_set:Nn \l__ufgrid_tab_list_tl {#1}
    }
    \bool_set_true:N \l__ufgrid_hmode_bool
   },
  debug-vgrid    .code:n = {\AddToShipoutPictureBG*{\AtTextUpperLeft{\showdebugpagegrid}}}
 }


%%calculate the vskip to insert
  %#1 = diff to a point on the grid
  %#2 = currently used vskip,
  %#3 = step
  % all values integers =dim in sp!
\sys_if_engine_luatex:TF
 {
  \cs_new:Nn \__ufgrid_calculate_vskip:nnn
   { \directlua { returntogrid.ufgridvskip (#1,#2,#3) } }
 }
 {
  \cs_new:Nn \__ufgrid_calculate_vskip:nnn
  {
   \int_eval:n
    {
     \int_mod:nn
      {
       #2 + #3 - \int_mod:nn { #1 }{ #3 }
      }
      {#3}
    }
   }
 }


\cs_new:Nn \__ufgrid_calculate_vskip:n %#1 =labelname
 {
  \__ufgrid_calculate_vskip:nnn
   { %diff
    \zposy{\l__ufgrid_setup_reference_tl} - \l__ufgrid_setup_offset_tl - \zposy{#1}
   }
   { %current vskip
    \zref@extractdefault{#1-vskip} {ufgridvskip} {0}
   }
   {
    \l__ufgrid_setup_step_tl
   }
 }

\cs_new_protected:Nn \__ufgrid_savepos:n %#1 label name
 {
  \leavevmode\strut
  \zsavepos{#1}
 }


\cs_new_protected:Nn \__ufgrid_insert_vskip:n %#1 label name
 {
  \par
  \int_compare:nT { \zposy{#1} != 0 }
   {
    %calculate the needed vskip base on the ypos and the old vskip
    \int_set:Nn \l__ufgrid_vskip_int
    {
     \__ufgrid_calculate_vskip:n { #1 }
    }
    \zref@labelbyprops {#1-vskip}{ufgridvskip}
    %insert the vskip
    \vspace*{\l__ufgrid_vskip_int sp}
    %\vskip \l__ufgrid_vskip_int sp\relax
    %\vskip 0pt
   }
 }

\cs_new_protected:Nn \__ufgrid_insert_ufgridstrut:n {\rule{0pt}{\dim_eval:n { \l__ufgrid_setup_step_tl sp + \topskip }}}


%vertical version
\cs_new_protected:Nn \__ufgrid_returntogrid_v:
{
   \bool_if:NT \l__ufgrid_autolabel_bool
   {
    \int_gincr:N \g__ufgrid_vpoint_int
    \tl_set:Nn  \l__ufgrid_label_tl
     {
      \g__ufgrid_prefix_vpoint_tl \int_use:N\g__ufgrid_vpoint_int
     }
   }
  \bool_if:NT\l__ufgrid_insert_vskip_bool
   {
    \__ufgrid_insert_vskip:n { \l__ufgrid_label_tl }
   }
  \bool_if:NT\l__ufgrid_savepos_bool
   {
    \__ufgrid_savepos:n { \l__ufgrid_label_tl }
   }
}


\cs_new_protected:Nn \__ufgrid_settabpositions:nn %#1 name #2 list of spaces
 {
  \seq_set_from_clist:Nn \l_tmpa_seq {#2}
  \seq_new:c {g__ufgrid_tabpos_#1_seq}
  \int_set:Nn\l__ufgrid_tempa_int {1}
  \makebox[0pt][l]
  {
   \seq_map_inline:Nn \l_tmpa_seq
    {
     \zsavepos{\g__ufgrid_prefix_tabpos_tl#1\int_use:N\l__ufgrid_tempa_int}
     \bool_if:NT \l__ufgrid_debug_tab_bool
     {
       \__ufgrid_debug_showtabs:n { #1 }
     }
     \hspace*{\dim_eval:n{##1}}
     \int_incr:N\l__ufgrid_tempa_int
    }
   \zsavepos{\g__ufgrid_prefix_tabpos_tl#1\int_use:N\l__ufgrid_tempa_int}
   \bool_if:NT \l__ufgrid_debug_tab_bool
     {
       \__ufgrid_debug_showtabs:n { #1 }
     }
  \int_step_inline:nnnn {1}{1}{\l__ufgrid_tempa_int}
   {
    \seq_gput_right:cx {g__ufgrid_tabpos_#1_seq} {\zposx{\g__ufgrid_prefix_tabpos_tl#1##1}}
   }
  }
 }



\cs_new_protected:Nn \__ufgrid_returntogrid_h:n %#1 name
{
 \seq_if_exist:cF { g__ufgrid_tabpos_#1_seq }
  {
    \msg_error:nnx { ufgrid } {tab-list-unknown} { #1}
  }
 \bool_if:NT \l__ufgrid_autolabel_bool
   {
    \int_gincr:N \g__ufgrid_hpoint_int
    \tl_set:Nn  \l__ufgrid_label_tl
     {
      \g__ufgrid_prefix_hpoint_tl \int_use:N\g__ufgrid_hpoint_int
     }
   }
 \leavevmode\zsavepos{\l__ufgrid_label_tl}
 \tl_set:Nx \l__ufgrid_curpos_tl
  {
   \zref@extractdefault {\l__ufgrid_label_tl} {posx} {-1}
  }
 \bool_if:NT \g__ufgrid_twoside_bool
 {
  \int_if_odd:nTF
   {
    \zref@extractdefault {\l__ufgrid_label_tl} {pagecnt} {1}
    -
    \zref@extractdefault {\g__ufgrid_prefix_tabpos_tl#1 1} {pagecnt} {1}
   }
   {
    % tab setting and current pos are on different page types.
    \int_if_even:nTF
    {
      \zref@extractdefault {\g__ufgrid_prefix_tabpos_tl#1 1} {pagecnt} {1}
    }
    {
      \int_set:Nn \l__ufgrid_tab_cor_int {\dim_to_decimal_in_sp:n {\oddsidemargin-\evensidemargin}}
    }
    {
      \int_set:Nn \l__ufgrid_tab_cor_int {\dim_to_decimal_in_sp:n {\evensidemargin-\oddsidemargin}}
    }
   }
   {
     \int_set:Nn \l__ufgrid_tab_cor_int {0}
   }
 }
 \int_compare:nNnF { \l__ufgrid_curpos_tl } {=}{-1}
  {
   \bool_set_false:N \l__ufgrid_tabfound_bool
   \int_step_inline:nn { \seq_count:c { g__ufgrid_tabpos_#1_seq }-1 }
   {
    \int_compare:nT
    { (\seq_item:cn { g__ufgrid_tabpos_#1_seq } { ##1 } + \l__ufgrid_tab_cor_int)
      <
      \l__ufgrid_curpos_tl
      <
      (\seq_item:cn { g__ufgrid_tabpos_#1_seq } { ##1 +1 } + \l__ufgrid_tab_cor_int)
    }
    {
     \bool_set_true:N \l__ufgrid_tabfound_bool
     \hspace*
      {
       \int_eval:n
        {
         \seq_item:cn { g__ufgrid_tabpos_#1_seq } { ##1 +1 } + \l__ufgrid_tab_cor_int
         -
         \l__ufgrid_curpos_tl
        }sp
      }
    }
   }
   \bool_if:NT \l__ufgrid_hfill_bool
   {
    \int_step_inline:nn { \seq_count:c { g__ufgrid_tabpos_#1_seq }}
    {
     \int_compare:nT
     {
      \seq_item:cn { g__ufgrid_tabpos_#1_seq } { ##1 } + \l__ufgrid_tab_cor_int
      =
      \l__ufgrid_curpos_tl
     }
     {
      \bool_set_true:N \l__ufgrid_tabfound_bool
     }
    }
    \bool_if:NF \l__ufgrid_tabfound_bool {\hfill}
   }
  }
}



\cs_new_protected:Nn \__ufgrid_returntogrid:n
{
 \group_begin:
 \bool_if:NT \g__ufgrid_active_bool
 {
  \keys_set:nn { ufgrid / use }
  {
   __autolabel,__savepos=true,__insert_vskip=true,hmode=false,
   #1
  }
  \bool_if:NTF \l__ufgrid_hmode_bool
   {
    \__ufgrid_returntogrid_h:n { \l__ufgrid_tab_list_tl }
   }
   {
    \__ufgrid_returntogrid_v:
   }
  }
 \group_end:
}


\NewDocumentCommand\returntogrid{ O {} }
 {
   \__ufgrid_returntogrid:n { #1 }
 }




\NewDocumentCommand\returntogridsetup { m }
 {
  \keys_set:nn { ufgrid / setup } { #1}
 }

\NewDocumentCommand \showdebugpagegrid {}
 {
 \cs_if_exist:NTF\tikzpicture
  {
  \begin{tikzpicture}[overlay]
   \draw[dotted,red]
     (0,0) --++ (\textwidth,0cm);
   \tl_set:Nx \l_tmpa_tl {\fp_eval:n {
    trunc (
     (\dim_to_decimal_in_sp:n {\textheight} -  \l__ufgrid_setup_offset_tl )
     /
    \l__ufgrid_setup_step_tl
         )
          }}
   \foreach\x in {0,1,2,...,\l_tmpa_tl}
    {
     \draw[red,dashed]
     (0,\fp_eval:n {-\l__ufgrid_setup_offset_tl-(\x*\l__ufgrid_setup_step_tl)}sp) --++ (\textwidth,0cm);
    }
  \end{tikzpicture}%
  }
  {
   \msg_warning:nn {ufgrid} {tikz-needed}
  }
 }

\cs_new_protected:Nn \__ufgrid_debug_showtabs:n
{
 \cs_if_exist:NTF\tikzpicture
  {
  \tikz[overlay]
   \l__ufgrid_debug_tab_draw_tl;
  }
  {
   \msg_warning:nn {ufgrid} {tikz-needed}
  }
 }


\AddToShipoutPictureBG*{\AtTextUpperLeft{\zsaveposy{\g__ufgrid_prefix_vpoint_tl textupperleft}}}

 \endinput
