% \iffalse meta-comment
%
%% File: l3draw-transforms.dtx
%
% Copyright (C) 2018-2026 The LaTeX Project
%
% It 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 file is part of the "l3experimental bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\RequirePackage{expl3}
\documentclass[full]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3draw-transforms} package\\ Transformations^^A
% }
%
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2026-06-18}
%
% \maketitle
%
% \begin{implementation}
%
% \section{\pkg{l3draw-transforms} implementation}
%
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=draw>
%    \end{macrocode}
%
% This sub-module covers more-or-less the same ideas as
% \texttt{pgfcoretransformations.code.tex}. At present, equivalents of the
% following are currently absent:
% \begin{itemize}
%   \item \cs{pgfgettransform}, \cs{pgfgettransformentries}: Awaiting use cases.
%   \item \cs{pgftransformlineattime}, \cs{pgftransformarcaxesattime}, 
%     \cs{pgftransformcurveattime}: Need to look at the use cases for
%     these to fully understand them.
%   \item \cs{pgftransformarrow}: Likely to be done when other arrow functions
%     are added.
%   \item \cs{pgftransformationadjustments}: Used mainly by CircuiTi\textit{k}Z
%     although also for shapes, likely needs more use cases before addressing.
%   \item \cs{pgflowlevelsynccm}, \cs{pgflowlevel}: Likely to be added when
%     use cases are encountered in other parts of the code.
%   \item \cs{pgfviewboxscope}: Seems very specialized, need to understand the
%     requirements here.
% \end{itemize}
%
% \begin{variable}{\l_@@_matrix_active_bool}
%   An internal flag to avoid redundant calculations.
%    \begin{macrocode}
\bool_new:N \l_@@_matrix_active_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}
%   {
%     \l_@@_matrix_a_fp,
%     \l_@@_matrix_b_fp,
%     \l_@@_matrix_c_fp,
%     \l_@@_matrix_a_fp,
%     \l_@@_xshift_dim,
%     \l_@@_yshift_dim
%   }
%   The active matrix and shifts.
%    \begin{macrocode}
\fp_new:N \l_@@_matrix_a_fp
\fp_new:N \l_@@_matrix_b_fp
\fp_new:N \l_@@_matrix_c_fp
\fp_new:N \l_@@_matrix_d_fp
\dim_new:N \l_@@_xshift_dim
\dim_new:N \l_@@_yshift_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\draw_transform_matrix_reset:, \draw_transform_shift_reset:}
%   Fast resetting.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_matrix_reset:
  {
    \fp_set:Nn \l_@@_matrix_a_fp { 1 }
    \fp_zero:N \l_@@_matrix_b_fp
    \fp_zero:N \l_@@_matrix_c_fp
    \fp_set:Nn \l_@@_matrix_d_fp { 1 }
    \bool_set_false:N \l_@@_matrix_active_bool
  }
\cs_new_protected:Npn \draw_transform_shift_reset:
  {
    \dim_zero:N \l_@@_xshift_dim
    \dim_zero:N \l_@@_yshift_dim
  }
\draw_transform_matrix_reset:
\draw_transform_shift_reset:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\draw_transform_matrix_absolute:nnnn}
% \begin{macro}{\draw_transform_shift_absolute:n}
% \begin{macro}{\@@_transform_shift_absolute:nn}
%   Setting the transform matrix is straight-forward, with just a bit
%   of expansion to sort out. With the mechanism active, the identity
%   matrix is set.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_matrix_absolute:nnnn #1#2#3#4
  {
    \fp_set:Nn \l_@@_matrix_a_fp {#1}
    \fp_set:Nn \l_@@_matrix_b_fp {#2}
    \fp_set:Nn \l_@@_matrix_c_fp {#3}
    \fp_set:Nn \l_@@_matrix_d_fp {#4}
    \bool_lazy_all:nTF
      {
        { \fp_compare_p:nNn \l_@@_matrix_a_fp = \c_one_fp }
        { \fp_compare_p:nNn \l_@@_matrix_b_fp = \c_zero_fp }
        { \fp_compare_p:nNn \l_@@_matrix_c_fp = \c_zero_fp }
        { \fp_compare_p:nNn \l_@@_matrix_d_fp = \c_one_fp }
      }
      { \bool_set_false:N \l_@@_matrix_active_bool }
      { \bool_set_true:N \l_@@_matrix_active_bool }
  }
\cs_new_protected:Npn \draw_transform_shift_absolute:n #1
  {
    \@@_point_process:nn
      { \@@_transform_shift_absolute:nn } { \draw_point:n {#1} }
  }
\cs_new_protected:Npn \@@_transform_shift_absolute:nn #1#2
  {
    \dim_set:Nn \l_@@_xshift_dim {#1}
    \dim_set:Nn \l_@@_yshift_dim {#2}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_transform_matrix:nnnn}
% \begin{macro}{\@@_transform:nnnn}
% \begin{macro}{\draw_transform_shift:n}
% \begin{macro}{\@@_transform_shift:nn}
%   Much the same story for adding to an existing matrix, with a bit of
%   pre-expansion so that the calculation uses \enquote{frozen} values.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_matrix:nnnn #1#2#3#4
  {
    \use:e
      {
        \@@_transform:nnnn
          { \fp_eval:n {#1} }
          { \fp_eval:n {#2} }
          { \fp_eval:n {#3} }
          { \fp_eval:n {#4} }
      }
  }
\cs_new_protected:Npn \@@_transform:nnnn #1#2#3#4
  {
    \use:e
      {
        \draw_transform_matrix_absolute:nnnn
          { #1 * \l_@@_matrix_a_fp + #2 * \l_@@_matrix_c_fp }
          { #1 * \l_@@_matrix_b_fp + #2 * \l_@@_matrix_d_fp }
          { #3 * \l_@@_matrix_a_fp + #4 * \l_@@_matrix_c_fp }
          { #3 * \l_@@_matrix_b_fp + #4 * \l_@@_matrix_d_fp }
        }
  }
\cs_new_protected:Npn \draw_transform_shift:n #1
  {
    \@@_point_process:nn
      { \@@_transform_shift:nn } { \draw_point:n {#1} }
  }
\cs_new_protected:Npn \@@_transform_shift:nn #1#2
  {
    \@@_transform_shift:nnnn
      \l_@@_xshift_dim
      \l_@@_yshift_dim
      {#1} {#2}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_transform_shift:nnnn}
%   Apply the current transformation matrix to the shift, then store
%   the resulting values: we may or may not have a non-zero starting
%   point here.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_transform_shift:nnnn #1#2#3#4
  {
    \dim_set:Nn \l_@@_xshift_dim
      {
        \fp_to_dim:n
          {
            #1 +
            ( #3 * \l_@@_matrix_a_fp + #4 * \l_@@_matrix_c_fp )
          }
      }
    \dim_set:Nn \l_@@_yshift_dim
      {
        \fp_to_dim:n
          {
            #2 +
            ( #3 * \l_@@_matrix_b_fp + #4 * \l_@@_matrix_d_fp )
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\draw_transform_matrix_invert:}
% \begin{macro}{\@@_transform_invert:n, \@@_transform_invert:e}
% \begin{macro}{\draw_transform_shift_invert:}
% \begin{macro}{\@@_transform_shift_invert:n, \@@_transform_shift_invert:e}
%   Standard mathematics: calculate the inverse affine transformation. 
%   For the matrix, the inverse is calculated and applied. For the shift, 
%   the inverse vector is evaluated as $-M^{-1} \cdot S$. We use 
%   \texttt{e}-type expansion to snapshot the current floating-point values 
%   of the matrix and shift prior to assignment.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_matrix_invert:
  {
    \bool_if:NT \l_@@_matrix_active_bool
      {
        \@@_transform_invert:e
          {
            \fp_eval:n
              { 
                1 /
                  (
                    \l_@@_matrix_a_fp * \l_@@_matrix_d_fp
                    - \l_@@_matrix_b_fp * \l_@@_matrix_c_fp
                  )
              }
          }
      }
  }
\cs_new_protected:Npn \@@_transform_invert:n #1
  {
    \use:e
      {
        \draw_transform_matrix_absolute:nnnn
          { \l_@@_matrix_d_fp * #1 }
          { -\l_@@_matrix_b_fp * #1 }
          { -\l_@@_matrix_c_fp * #1 }
          { \l_@@_matrix_a_fp * #1 }
      }
  }
\cs_generate_variant:Nn \@@_transform_invert:n { e }
\cs_new_protected:Npn \draw_transform_shift_invert:
  {
    \bool_if:NTF \l_@@_matrix_active_bool
      {
        \@@_transform_shift_invert:e
          {
            \fp_eval:n
              {
                1 /
                  (
                    \l_@@_matrix_a_fp * \l_@@_matrix_d_fp
                    - \l_@@_matrix_b_fp * \l_@@_matrix_c_fp
                  )
              }
          }
      }
      {
        \dim_set:Nn \l_@@_xshift_dim { -\l_@@_xshift_dim }
        \dim_set:Nn \l_@@_yshift_dim { -\l_@@_yshift_dim }
      }
  }
\cs_new_protected:Npn \@@_transform_shift_invert:n #1
  {
    \use:e
      {
        \@@_transform_shift:nn
          {
            \fp_to_dim:n
              {
                (
                    \l_@@_matrix_c_fp * \l_@@_yshift_dim
                  - \l_@@_matrix_d_fp * \l_@@_xshift_dim
                ) * #1
              }
          }
          {
            \fp_to_dim:n
              {
                (
                    \l_@@_matrix_b_fp * \l_@@_xshift_dim
                  - \l_@@_matrix_a_fp * \l_@@_yshift_dim
                ) * #1
              }
          }
      }
  }
\cs_generate_variant:Nn \@@_transform_shift_invert:n { e }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_transform_triangle:nnn}
%   Simple maths to move the canvas origin to |#1| and the two axes to
%   |#2| and |#3|.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_triangle:nnn #1#2#3
  {
    \@@_point_process:nnnn
       { \@@_transform_triangle:nnnnnn }
       { \draw_point:n {#1} }
       { \draw_point:n {#2} }
       { \draw_point:n {#3} }
  }
\cs_new_protected:Npn \@@_transform_triangle:nnnnnn #1#2#3#4#5#6
  {
    \use:e
      {
        \draw_transform_matrix:nnnn
          { #3 - #1 }
          { #4 - #2 }
          { #5 - #1 }
          { #6 - #2 }
        \draw_transform_shift:n { #1 , #2 }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {\draw_transform_scale:n, \draw_transform_xscale:n, \draw_transform_yscale:n}
% \begin{macro}
%   {\draw_transform_xshift:n, \draw_transform_yshift:n}
% \begin{macro}
%   {\draw_transform_xslant:n, \draw_transform_yslant:n}
%   Lots of shortcuts.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_scale:n #1
  { \draw_transform_matrix:nnnn { #1 } { 0 } { 0 } { #1 } }
\cs_new_protected:Npn \draw_transform_xscale:n #1
  { \draw_transform_matrix:nnnn { #1 } { 0 } { 0 } { 1 } }
\cs_new_protected:Npn \draw_transform_yscale:n #1
  { \draw_transform_matrix:nnnn { 1 } { 0 } { 0 } { #1 } }
\cs_new_protected:Npn \draw_transform_xshift:n #1
  { \draw_transform_shift:n { #1 , 0pt } }
\cs_new_protected:Npn \draw_transform_yshift:n #1
  { \draw_transform_shift:n { 0pt , #1 } }
\cs_new_protected:Npn \draw_transform_xslant:n #1
  { \draw_transform_matrix:nnnn { 1 } { 0 } { #1 } { 1 } }
\cs_new_protected:Npn \draw_transform_yslant:n #1
  { \draw_transform_matrix:nnnn { 1 } { #1 } { 0 } { 1 } }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_transform_rotate:n}
% \begin{macro}
%   {
%     \@@_transform_rotate:n, \@@_transform_rotate:e,
%     \@@_transform_rotate:nn, \@@_transform_rotate:ee
%   } 
%   Slightly more involved: evaluate the angle only once, and the sine and
%   cosine only once.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_transform_rotate:n #1
  { \@@_transform_rotate:e { \fp_eval:n {#1} } }
\cs_new_protected:Npn \@@_transform_rotate:n #1
  {
    \@@_transform_rotate:ee
      { \fp_eval:n { cosd(#1) } }
      { \fp_eval:n { sind(#1) } }
  }
\cs_generate_variant:Nn \@@_transform_rotate:n { e }
\cs_new_protected:Npn \@@_transform_rotate:nn #1#2
  { \draw_transform_matrix:nnnn {#1} {#2} { -#2 } { #1 } }
\cs_generate_variant:Nn \@@_transform_rotate:nn { ee }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
