%% This is file `stocksize.sty'
%%
%% Copyright (C) 2024–25 by João M. Lourenço <joao.lourenco@fct.unl.pt>
%%
%% This file may be distributed and/or modified under the conditions of
%% the LaTeX Project Public License, either version 1.3c of this license
%% or (at your option) any later version.
%%
\def\fileversion{2.0.1}
\def\filedate{2026/01/01}
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage{stocksize}
                    {\filedate}
                    {\fileversion}
                    {Synchronize physical with logical paper size (Stack Aware)}

% ============================================================================
% 1. DEPENDENCIES
% ============================================================================
\RequirePackage{geometry}
\RequirePackage{l3keys2e}

% ============================================================================
% 2. VARIABLE DEFINITIONS
% ============================================================================
% Stack counter for nested geometry handling
\newcount\Gm@stackcnt
\Gm@stackcnt=\z@

% Expl3 variables for option handling
\clist_new:N \l__geostack_options_clist
\clist_new:N \l__geostack_defaults_clist
\bool_new:N  \l__geostack_patch_bool

% ============================================================================
% 3. INTERNAL HELPERS
% ============================================================================

% ----------------------------------------------------------------------------
% Helper: Synchronize Physical Paper Size
% ----------------------------------------------------------------------------
% This function ensures that the physical PDF "canvas" (PDF MediaBox) matches
% the logical "paper" dimensions defined by geometry.
\cs_new_protected:Nn \__geostack_sync_physical_size:
  {
    % 1. Update LaTeX standard paper dimensions to match geometry's layout
    \setlength{\paperwidth}{\the\Gm@layoutwidth}
    \setlength{\paperheight}{\the\Gm@layoutheight}
    
    % 2. Sync PDF driver specific dimensions (pdfTeX, LuaTeX, XeTeX)
    \cs_if_exist:NT \pdfpagewidth
      {
        \setlength{\pdfpagewidth}{\paperwidth}
        \setlength{\pdfpageheight}{\paperheight}
      }
    \cs_if_exist:NT \pagewidth
      {
        \setlength{\pagewidth}{\paperwidth}
        \setlength{\pageheight}{\paperheight}
      }
    
    % 3. Sync memoir class or other packages using \stockwidth
    \cs_if_exist:NT \stockwidth
      {
        \setlength{\stockwidth}{\paperwidth}
        \setlength{\stockheight}{\paperheight}
      }
      
    % 4. Force geometry to recalculate its internal layout logic
    \cs_if_exist:cT { Gm@changelayout }
      {
        \use:c { Gm@changelayout }
      }
  }

% ============================================================================
% 4. CORE LOGIC: GEOMETRY REDEFINITIONS (STACK MECHANISM)
% ============================================================================

% ----------------------------------------------------------------------------
% Redefine \newgeometry to support LIFO Stacking
% ----------------------------------------------------------------------------
% Standard \newgeometry destroys the previous state. We redefine it to:
% 1. Save the CURRENT state to a dynamic stack macro.
% 2. Apply the NEW state.
\renewcommand{\newgeometry}[1]{%
  \clearpage
  % --- A. Stack Push Logic ---
  % Initialize \Gm@restore locally to ensure cleanliness
  \def\Gm@restore{}%
  
  % Call \Gm@save (geometry internal). 
  % This populates \Gm@restore with the commands needed to restore the CURRENT layout.
  \Gm@save
  
  % Increment stack depth
  \global\advance\Gm@stackcnt\@ne
  
  % Save the restoration instructions into a dynamic macro named \Gm@stack@<n>
  % We use \xdef to expand \Gm@restore immediately (converting registers to values).
  \global\expandafter\xdef\csname Gm@stack@\the\Gm@stackcnt\endcsname{\Gm@restore}%
  
  % --- B. Apply New Geometry (Standard geometry logic) ---
  \Gm@restore@org                 % Reset to preamble defaults
  \Gm@initnewgm
  \Gm@newgmtrue
  \setkeys{Gm}{#1}%               % Process user arguments
  \Gm@newgmfalse
  \Gm@process
  \ifnum\mag=\@m\else\Gm@magtooffset\fi
  \Gm@changelayout
  \Gm@showparams{newgeometry}}

% ----------------------------------------------------------------------------
% Redefine \restoregeometry to support LIFO Stacking
% ----------------------------------------------------------------------------
% Standard \restoregeometry only goes back to preamble defaults. We redefine it to:
% 1. Check if we are in a nested state (stack > 0).
% 2. If yes, pop the last state. If no, reset to preamble.
\renewcommand{\restoregeometry}{%
  \clearpage
  % --- Stack Pop Logic ---
  \ifnum\Gm@stackcnt>\z@
    % Case 1: Stack is not empty. 
    % Execute the saved macro for the current level (restoring layout).
    \csname Gm@stack@\the\Gm@stackcnt\endcsname
    % Decrement the counter
    \global\advance\Gm@stackcnt\m@ne
  \else
    % Case 2: Stack is empty. 
    % Fallback to standard geometry behavior (restore preamble settings).
    \Gm@restore@pkg
  \fi
  % Recalculate layout
  \Gm@changelayout}

% ============================================================================
% 5. USER INTERFACE
% ============================================================================

% ----------------------------------------------------------------------------
% Command: \newstocksize{options}
% ----------------------------------------------------------------------------
% Wrapper around \newgeometry that also handles:
% 1. The 'keepmargins' logic (preserving current margins in new size).
% 2. Syncing the physical page size after the layout change.
\NewDocumentCommand {\newstocksize } { m }
  {
    % --- STEP 1: Parse Options & Handle 'keepmargins' ---
    \clist_set:Nn \l__geostack_options_clist { #1 }
    \clist_clear:N \l__geostack_defaults_clist
    
    \clist_if_in:NnTF \l__geostack_options_clist { keepmargins }
      {
        % A. Capture CURRENT margins from internal geometry macros
        \clist_put_right:Nx \l__geostack_defaults_clist { lmargin = \use:c{Gm@lmargin} }
        \clist_put_right:Nx \l__geostack_defaults_clist { rmargin = \use:c{Gm@rmargin} }
        \clist_put_right:Nx \l__geostack_defaults_clist { tmargin = \use:c{Gm@tmargin} }
        \clist_put_right:Nx \l__geostack_defaults_clist { bmargin = \use:c{Gm@bmargin} }
        
        % B. Remove 'keepmargins' (it's not a valid geometry option)
        \clist_remove_all:Nn \l__geostack_options_clist { keepmargins }
      }
      { }
      
    % Merge calculated defaults with user's explicit options
    \clist_put_right:NV \l__geostack_defaults_clist \l__geostack_options_clist
    
    % --- STEP 2: Change Geometry (Push to Stack) ---
    % \newgeometry (our patched version) updates logic and saves old state
    \exp_args:NV \newgeometry \l__geostack_defaults_clist
    
    % --- STEP 3: Sync Physical Output Size ---
    \__geostack_sync_physical_size:
  }

% ----------------------------------------------------------------------------
% Command: \restorestocksize
% ----------------------------------------------------------------------------
% Restores previous layout and syncs physical size.
\NewDocumentCommand {\restorestocksize } {}
  {
    % --- STEP 1: Restore Geometry (Pop from Stack) ---
    \restoregeometry
    
    % --- STEP 2: Sync Physical Output Size ---
    \__geostack_sync_physical_size:
  }

% ============================================================================
% 6. PACKAGE CONFIGURATION & PATCHING
% ============================================================================

% Define keys
\keys_define:nn { stocksize }
  {
    patch-geometry .bool_set:N = \l__geostack_patch_bool,
    patch-geometry .default:n = true,
  }

% Process package options
\ProcessKeysPackageOptions { stocksize }

% Apply patches if 'patch-geometry' was requested
% This appends the sync command to standard geometry commands.
\bool_if:NT \l__geostack_patch_bool
  {
    \apptocmd{\newgeometry}
      {\__geostack_sync_physical_size:}
      {}{\PackageWarning{stocksize}{Failed to patch newgeometry}}
      
    \apptocmd{\restoregeometry}
      {\__geostack_sync_physical_size:}
      {}{\PackageWarning{stocksize}{Failed to patch restoregeometry}}
      
    \apptocmd{\loadgeometry}
      {\__geostack_sync_physical_size:}
      {}{\PackageWarning{stocksize}{Failed to patch loadgeometry}}
  }

\endinput
