%D \module
%D   [       file=grph-trf,
%D        version=2006.08.26, % overhaul/split of 1997.03.31 core-fig
%D          title=\CONTEXT\ Graphic Macros,
%D       subtitle=Transformations,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\writestatus{loading}{ConTeXt Graphic Macros / Transformations}

\unprotect

%D We probably use too many dimens as the width calculations can go away. Some of
%D this is an inheritance of limited backends (some supported fractions, some
%D 1000's, some dimentions) so we calculate all of them. Nowadays scaling is always
%D available so we could simplify the code. On the other hand, we now get some extra
%D values for free.
%D
%D We could move the calculations to \LUA\ and clean up this lot anyway. On the
%D other hand, there is some danger of messing up so it has a real low priority.

\registerctxluafile{grph-trf}{autosuffix}

% See below

\newdimension\d_grph_rotate_x_size
\newdimension\d_grph_rotate_y_size
\newdimension\d_grph_rotate_x_offset
\newdimension\d_grph_rotate_y_offset
\newdimension\d_grph_rotate_x_position
\newdimension\d_grph_rotate_y_position

\newdimension\d_grph_rotate_new_width
\newdimension\d_grph_rotate_new_height
\newdimension\d_grph_rotate_new_depth

% local:

\newdimension\d_grph_scale_x_size
\newdimension\d_grph_scale_y_size
\newdimension\d_grph_scale_x_offset
\newdimension\d_grph_scale_y_offset

\newdimension\d_grph_scale_h_size
\newdimension\d_grph_scale_v_size

\newconditional\c_grph_scale_done
\newconditional\c_grph_scale_scaling_done
\newconditional\c_grph_scale_limit_factors \c_grph_scale_limit_factors\conditionaltrue

\newdimension\d_grph_scale_wd
\newdimension\d_grph_scale_ht
\newdimension\d_grph_scale_dp

\newconditional\c_grph_scale_swap_factor % global

\newdimension\d_grph_scale_used_x_size   % global
\newdimension\d_grph_scale_used_y_size   % global

\newinteger\c_grph_scale_used_x_scale
\newinteger\c_grph_scale_used_y_scale

\let\m_grph_scale_used_x_scale\!!plusone
\let\m_grph_scale_used_y_scale\!!plusone

\newdimension\d_grph_scale_outer_v_size % we cannot manipulate any global vsize !

% scratch:

\lettonothing\m_grph_scale_temp
\lettonothing\m_grph_scale_temp_x
\lettonothing\m_grph_scale_temp_y

% public:

\mutable\let\finalscaleboxxscale \!!plusone
\mutable\let\finalscaleboxyscale \!!plusone
\mutable\let\finalscaleboxwidth  \!!zeropoint
\mutable\let\finalscaleboxheight \!!zeropoint

% we can let sx/sy win (first check)

\installcorenamespace{scale}
\installcorenamespace{scalegrid}
\installcorenamespace{scalenorm}
\installcorenamespace{scalefact}

\installcommandhandler \??scale {scale} \??scale % we can have instances

\setupscale
  [\c!sx=\scaleparameter\c!s,
   \c!sy=\scaleparameter\c!s,
   \c!s=1,
  %\c!scale=,
  %\c!xscale=,
  %\c!yscale=,
  %\c!width=,
  %\c!height=,
  %\c!lines=,
  %\c!factor=,
  %\c!hfactor=,
  %\c!wfactor=,
  %\c!grid=,
  %\c!equalwidth=,
  %\c!equalheight=,
   \c!maxwidth=\scaleparameter\c!width,
   \c!maxheight=\scaleparameter\c!height]

\newinteger\c_grph_scale_nesting

\def\grph_scale_set_nesting
  {\advanceby\c_grph_scale_nesting\plusone
   \letcsname\??scale>\the\c_grph_scale_nesting:\s!parent\endcsname\??scale
   \cdef\currentscale{>\the\c_grph_scale_nesting}}

\permanent\tolerant\protected\def\scale[#S#1]#*[#S#2]%
  {\bgroup
   \ifarguments
     \grph_scale_set_nesting
   \or
     \ifhastok={#1}%
       \grph_scale_set_nesting
       \setupcurrentscale[#1]%
     \else
       \cdef\currentscale{#1}%
       \ifempty\currentscale
         \grph_scale_set_nesting
       \fi
     \fi
   \or
     \cdef\currentscale{#1}%
     \ifempty\currentscale
       \grph_scale_set_nesting
     \fi
     \setupcurrentscale[#2]%
   \fi
   %
   \dowithnextboxcs\grph_scale_finish\naturalhbox} % intercept direction

\def\grph_scale_finish
  {% todo: p_scale_
   \edef\p_scale      {\scaleparameter\c!scale      }%
   \edef\p_xscale     {\scaleparameter\c!xscale     }%
   \edef\p_yscale     {\scaleparameter\c!yscale     }%
   \edef\p_width      {\scaleparameter\c!width      }%
   \edef\p_height     {\scaleparameter\c!height     }%
 % \edef\p_depth      {\scaleparameter\c!depth      }% used once
   \edef\p_lines      {\scaleparameter\c!lines      }%
   \edef\p_factor     {\scaleparameter\c!factor     }%
   \edef\p_hfactor    {\scaleparameter\c!hfactor    }%
   \edef\p_wfactor    {\scaleparameter\c!wfactor    }%
 % \edef\p_grid       {\scaleparameter\c!grid       }% used once
   \edef\p_maxwidth   {\scaleparameter\c!maxwidth   }%
   \edef\p_maxheight  {\scaleparameter\c!maxheight  }%
   \edef\p_sx         {\scaleparameter\c!sx         }%
   \edef\p_sy         {\scaleparameter\c!sy         }%
   \edef\p_equalwidth {\scaleparameter\c!equalwidth }%
   \edef\p_equalheight{\scaleparameter\c!equalheight}%
   %
   \d_grph_scale_dp\dp\nextbox
   \ifzeropt\d_grph_scale_dp
     % nothing to do
   \orelse\ifcstok{\scaleparameter\c!depth}\v!no
     \setbox\nextbox\naturalhpack{\raise\d_grph_scale_dp\box\nextbox}% new
    %\d_grph_scale_dp\dp\nextbox
   \fi
   \d_grph_scale_wd\wd\nextbox
   \d_grph_scale_ht\ht\nextbox
   \d_grph_scale_dp\dp\nextbox
   %
   \glet\finalscaleboxxscale \!!plusone
   \glet\finalscaleboxyscale \!!plusone
   \xdef\finalscaleboxwidth {\the\d_grph_scale_wd}%
   \xdef\finalscaleboxheight{\the\d_grph_scale_ht}%
   %
   \forgetall
   \dontcomplain
   %
   \c_grph_scale_done\conditionalfalse
   \grph_scale_calculate
   \ifconditional\c_grph_scale_done
     \grph_scale_apply
   \fi
   \grph_scale_position
   %
   \box\nextbox
   \egroup}

\def\grph_scale_apply
  {\d_grph_scale_wd\finalscaleboxxscale\d_grph_scale_wd
   \d_grph_scale_ht\finalscaleboxyscale\d_grph_scale_ht
   \d_grph_scale_dp\finalscaleboxyscale\d_grph_scale_dp
   \ifdim\d_grph_scale_wd=\wd\nextbox
     \ifdim\d_grph_scale_ht=\ht\nextbox
       \ifdim\d_grph_scale_dp=\dp\nextbox
        % \grph_scale_apply_nop
       \else
          \grph_scale_apply_yes
       \fi
     \else
       \grph_scale_apply_yes
     \fi
   \else
     \grph_scale_apply_yes
   \fi}

%D Test case for \type {\signalcharacter} from \type {musings.tex}:
%D
%D \starttyping
%D \setuphead[chapter][color=darkred,style=\bfd,command=\ChapterCommand]
%D \setuphead[section][color=darkred,style=\bfb]
%D \protected\def\ChapterCommand#1#2{\scale[height=2cm]{#2}}
%D \starttext
%D     \starttitle[title={CHAPTER}]
%D         \startsubject[title={SECTION}]
%D         \stopsubject
%D     \stoptitle
%D \stoptext
%D \stoptyping

\def\grph_scale_apply_yes
  {\setbox\nextbox\hcontainer\bgroup
     \dostartscaling \finalscaleboxxscale \finalscaleboxyscale
     \smashedbox\nextbox
     \dostopscaling
     % to get colors right as we group in pdf
     \signalcharacter
   \egroup
   \wd\nextbox\d_grph_scale_wd
   \ht\nextbox\d_grph_scale_ht
   \dp\nextbox\d_grph_scale_dp}

\lettonothing\m_grph_scale_stamp_a
\lettonothing\m_grph_scale_stamp_b
\def         \m_grph_scale_stamp_c{11}

\def\grph_scale_calculate
  {\ifdim\d_grph_scale_ht>\zeropoint \ifdim\d_grph_scale_wd>\zeropoint
     \edef\m_grph_scale_stamp_a{\p_scale\p_xscale\p_yscale\p_factor\p_wfactor\p_hfactor\p_lines\p_width\p_height}%
     \edef\m_grph_scale_stamp_b{\p_sx\p_sy}%
     \ifempty\m_grph_scale_stamp_a
       \ifx\m_grph_scale_stamp_b\m_grph_scale_stamp_c
         % no scaling, don't change this (previous attempts failed anyway)
         \insidefloattrue % trick
         \grph_scale_calculations_yes
       \else
         \grph_scale_check_sx_sy
         \grph_scale_calculations_nop
       \fi
     \else
       \ifempty\m_grph_scale_stamp_b
         % no need to check further
       \else
         \grph_scale_check_sx_sy
       \fi
       \grph_scale_calculations_yes
     \fi
   \fi \fi}

\def\grph_scale_check_sx_sy
  {\ifdim\p_sx\onepoint=\onepoint\else\edef\p_width {\todimension{\p_sx\d_grph_scale_wd}}\fi
   \ifdim\p_sy\onepoint=\onepoint\else\edef\p_height{\todimension{\p_sy\d_grph_scale_ht}}\fi}

\def\grph_scale_rounded#1%
  {\thewithoutunit\dimexpr#1\points*100+32768\scaledpoint\relax} % hm

\def\grph_scale_calculations_nop
  {\c_grph_scale_done\conditionaltrue
   \xdef\finalscaleboxwidth {\todimension{\p_sx\d_grph_scale_wd}}%
   \xdef\finalscaleboxheight{\todimension{\p_sy\d_grph_scale_ht}}%
   \glet\finalscaleboxxscale\p_sx
   \glet\finalscaleboxyscale\p_sy
   \ifempty\finalscaleboxxscale\let\finalscaleboxxscale\!!plusone\fi
   \ifempty\finalscaleboxyscale\let\finalscaleboxyscale\!!plusone\fi}

\let\grph_scale_calculations_report\relax

\def\grph_scale_calculations_yes
  {\c_grph_scale_done\conditionaltrue
   % initial values
   \d_grph_scale_x_offset\zeropoint
   \d_grph_scale_y_offset\zeropoint
   \d_grph_scale_x_size  \d_grph_scale_wd
   \d_grph_scale_y_size  \d_grph_scale_ht % alleen ht wordt geschaald!
   % final values
   \global\d_grph_scale_used_x_size \zeropoint % see note * (core-fig)
   \global\d_grph_scale_used_y_size \zeropoint % see note * (core-fig)
   \c_grph_scale_used_x_scale       \plusone   % see note * (core-fig)
   \c_grph_scale_used_y_scale       \plusone   % see note * (core-fig)
   \let\m_grph_scale_used_x_scale   \!!plusone
   \let\m_grph_scale_used_y_scale   \!!plusone
   % preparations
   \c_grph_scale_scaling_done\conditionalfalse
   \grph_scale_check_parameters
   % calculators
   % beware, they operate in sequence, and calculate missing dimensions / messy
   % grph_scale_by_nature % when? needed?
   \ifconditional\c_grph_scale_scaling_done\else\grph_scale_by_factor   \fi
   \ifconditional\c_grph_scale_scaling_done\else\grph_scale_by_scale    \fi
   \ifconditional\c_grph_scale_scaling_done\else\grph_scale_by_dimension\fi
   % used in actual scaling
   \xdef\finalscaleboxwidth  {\the\d_grph_scale_used_x_size}%
   \xdef\finalscaleboxheight {\the\d_grph_scale_used_y_size}%
   \glet\finalscaleboxxscale \m_grph_scale_used_x_scale
   \glet\finalscaleboxyscale \m_grph_scale_used_y_scale
   \grph_scale_calculations_report}

\defcsname\??scalegrid\v!yes     \endcsname{\getnoflines   \d_grph_scale_used_y_size\edef\p_height{\tointeger{\noflines\lineheight}}}
\defcsname\??scalegrid\v!height  \endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\todimension{\noflines\lineheight+\strutdepth}}}
\defcsname\??scalegrid\v!depth   \endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\todimension{\noflines\lineheight-\strutdepth}}}
\defcsname\??scalegrid\v!halfline\endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\todimension{\noflines\lineheight+.5\lineheight}}}
\defcsname\??scalegrid\v!fit     \endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\tointeger{\noflines\lineheight}}}
\letcsname\??scalegrid\empty     \endcsname\donothing

\def\grph_scale_check_parameters % resolve self referencing loops
  {\ifempty\p_maxwidth \else \edef\p_maxwidth {\todimension{\p_maxwidth        }}\fi
   \ifempty\p_maxheight\else \edef\p_maxheight{\todimension{\p_maxheight       }}\fi
   \ifempty\p_lines    \else \edef\p_height   {\todimension{\p_lines\lineheight}}\fi
   \begincsname\??scalegrid\scaleparameter\c!grid\endcsname}

\def\grph_scale_by_nature % where ! ! ! ! !
  {\ifempty\p_width \else \global\d_grph_scale_used_x_size{\p_width }\fi
   \ifempty\p_height\else \global\d_grph_scale_used_y_size{\p_height}\fi
   \ifempty\p_scale \else        \c_grph_scale_used_x_scale\p_scale
                                 \c_grph_scale_used_y_scale\p_scale \fi
   \ifempty\p_xscale\else        \c_grph_scale_used_x_scale\p_xscale\fi
   \ifempty\p_yscale\else        \c_grph_scale_used_y_scale\p_yscale\fi}

% \defineexternalfigure[width-6][factor=auto,maxwidth=\textheight,maxheight=\textwidth]
% \defineexternalfigure[width-7][factor=auto,maxwidth=\textwidth,maxheight=\textheight]
% \placefigure{none}{\rotate[frame=on,offset=overlay]{\externalfigure[t:/sources/cow.pdf][width-6]}} \page
% \placefigure{none}{\framed[frame=on,offset=overlay]{\externalfigure[t:/sources/cow.pdf][width-7]}}

%D The \type {min} option makes sure that the smallest available space determines
%D the max size (so we can get a bleed on the other axis):
%D
%D \startlinecorrection
%D \startcombination[nx=2,ny=2,distance=4cm]
%D     {\externalfigure[hacker][factor=max,maxwidth=6cm,maxheight=6cm]} {}
%D     {\externalfigure[mill]  [factor=max,maxwidth=6cm,maxheight=6cm]} {}
%D     {\externalfigure[hacker][factor=min,maxwidth=6cm,maxheight=6cm]} {}
%D     {\externalfigure[mill]  [factor=min,maxwidth=6cm,maxheight=6cm]} {}
%D \stopcombination
%D \stoplinecorrection

% todo: use posits

\def\m_grph_scale_factor_set{\v!min,\v!max,\v!fit,\v!broad,\v!auto} % can be an \edef

\def\grph_scale_by_factor
  {\ifinset\p_factor\m_grph_scale_factor_set
     \grph_scale_by_factor_a
   \orelse\ifinset\p_hfactor\m_grph_scale_factor_set
     \grph_scale_by_factor_b
   \orelse\ifinset\p_wfactor\m_grph_scale_factor_set
     \grph_scale_by_factor_c
   \else
     \grph_scale_by_factor_d
   \fi}

\def\grph_scale_by_factor_a
  {\grph_scale_apply_size
   \ifdim\d_grph_scale_x_size >\d_grph_scale_y_size
     \grph_scale_calculate_norm  \d_grph_scale_used_x_size\p_factor\p_maxwidth\hsize\d_grph_scale_h_size
     \grph_scale_calculate_scales\d_grph_scale_used_x_size\d_grph_scale_x_size
     \d_grph_scale_used_y_size\m_grph_scale_used_x_scale\d_grph_scale_y_size
   \else
     \grph_scale_calculate_norm  \d_grph_scale_used_y_size\p_factor\p_maxheight\d_grph_scale_outer_v_size\d_grph_scale_v_size
     \grph_scale_calculate_scales\d_grph_scale_used_y_size\d_grph_scale_y_size
     \d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size
   \fi
   \grph_scale_by_factor_indeed}

\def\grph_scale_by_factor_b
  {\grph_scale_apply_size
   \grph_scale_calculate_norm  \d_grph_scale_used_y_size\p_hfactor\p_maxheight\d_grph_scale_outer_v_size\d_grph_scale_v_size
   \grph_scale_calculate_scales\d_grph_scale_used_y_size\d_grph_scale_y_size
   \d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size
   \grph_scale_by_factor_indeed}

\def\grph_scale_by_factor_c
  {\grph_scale_apply_size
   \grph_scale_calculate_norm  \d_grph_scale_used_x_size\p_wfactor\p_maxwidth\hsize\d_grph_scale_h_size
   \grph_scale_calculate_scales\d_grph_scale_used_x_size\d_grph_scale_x_size
   \d_grph_scale_used_y_size\m_grph_scale_used_x_scale\d_grph_scale_y_size
   \grph_scale_by_factor_indeed}

\def\grph_scale_by_factor_d
  {\grph_scale_calculate_norm\d_grph_scale_used_y_size\p_factor \p_height \textheight\d_grph_scale_v_size
   \grph_scale_calculate_norm\d_grph_scale_used_y_size\p_hfactor\p_height \textheight\d_grph_scale_v_size
   \grph_scale_calculate_norm\d_grph_scale_used_x_size\p_wfactor\p_width  \hsize     \hsize}

\def\grph_scale_by_factor_indeed
  {\grph_scale_calculate_fact\p_factor
   \c_grph_scale_scaling_done\conditionaltrue
   \ifconditional\c_grph_scale_limit_factors
     \ifdim\d_grph_scale_used_x_size\ifconditional\c_grph_scale_swap_factor<\else>\fi\d_grph_scale_h_size
       \global\d_grph_scale_used_y_size\zeropoint
       \global\d_grph_scale_used_x_size\d_grph_scale_h_size
     \orelse\ifdim\d_grph_scale_used_y_size\ifconditional\c_grph_scale_swap_factor<\else>\fi\d_grph_scale_v_size
       \global\d_grph_scale_used_x_size\zeropoint
       \global\d_grph_scale_used_y_size\d_grph_scale_v_size
     \fi
   \fi
   \grph_scale_by_dimension}

\def\grph_scale_by_scale
  {\edef\m_grph_scale_temp{\p_scale\p_xscale\p_yscale}%
   \ifempty\m_grph_scale_temp \else
     \grph_scale_apply_scale\m_grph_scale_used_x_scale\p_xscale
     \grph_scale_apply_scale\m_grph_scale_used_y_scale\p_yscale
   % \global\d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size\relax % no global needed here
   % \global\d_grph_scale_used_y_size\m_grph_scale_used_y_scale\d_grph_scale_y_size\relax % no global needed here
     % wrong: we need to recalculate the scale
     \global\d_grph_scale_used_x_size\zeropoint
     \global\d_grph_scale_used_y_size\zeropoint
     %
     \ifempty\p_maxwidth
       \ifempty\p_maxheight\orelse\ifdim\d_grph_scale_y_size>\p_maxheight\relax
         \global\d_grph_scale_used_y_size\p_maxheight
       \fi
     \orelse\ifdim\d_grph_scale_x_size>\p_maxwidth\relax
       \global\d_grph_scale_used_x_size\p_maxwidth
     \fi
   \fi}

\def\grph_scale_by_dimension
  {\ifdim\d_grph_scale_used_x_size>\zeropoint
     \ifdim\d_grph_scale_used_y_size>\zeropoint
       \grph_scale_by_dimension_a
     \else
       \grph_scale_by_dimension_b
     \fi
   \else
     \ifdim\d_grph_scale_used_y_size>\zeropoint
       \grph_scale_by_dimension_c
     \else
       \grph_scale_by_dimension_d
     \fi
   \fi}

\def\grph_scale_by_dimension_a
  {\grph_scale_by_dimension_indeed
     {\grph_scale_calculate_scale\m_grph_scale_used_y_scale\d_grph_scale_used_y_size\d_grph_scale_y_size
      \grph_scale_calculate_scale\m_grph_scale_used_x_scale\d_grph_scale_used_x_size\d_grph_scale_x_size}%
     {\grph_scale_calculate_scale\m_grph_scale_used_y_scale\d_grph_scale_used_y_size\d_grph_scale_y_size
      \grph_scale_calculate_scale\m_grph_scale_used_x_scale\d_grph_scale_used_x_size\d_grph_scale_x_size}%
     {\grph_scale_calculate_scale\m_grph_scale_used_y_scale\d_grph_scale_used_y_size\d_grph_scale_y_size
      \grph_scale_calculate_scale\m_grph_scale_used_x_scale\d_grph_scale_used_x_size\d_grph_scale_x_size}}

\def\grph_scale_by_dimension_b
  {\grph_scale_by_dimension_indeed
     {\grph_scale_calculate_scales\d_grph_scale_used_x_size\d_grph_scale_x_size
      \d_grph_scale_used_y_size\m_grph_scale_used_x_scale\d_grph_scale_y_size}%
     {\grph_scale_calculate_scales\d_grph_scale_used_x_size\d_grph_scale_x_size
      \d_grph_scale_used_y_size\m_grph_scale_used_x_scale\d_grph_scale_y_size}%
     {\grph_scale_calculate_scales\d_grph_scale_used_x_size\d_grph_scale_x_size
      \d_grph_scale_used_y_size\m_grph_scale_used_x_scale\d_grph_scale_y_size}}

\def\grph_scale_by_dimension_c
  {\grph_scale_by_dimension_indeed % weird .. three same cases
     {\grph_scale_calculate_scales\d_grph_scale_used_y_size\d_grph_scale_y_size
      \d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size}%
     {\grph_scale_calculate_scales\d_grph_scale_used_y_size\d_grph_scale_y_size
      \d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size}%
     {\grph_scale_calculate_scales\d_grph_scale_used_y_size\d_grph_scale_y_size
      \d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size}}

\def\grph_scale_by_dimension_d
  {\grph_scale_by_dimension_indeed
     {\grph_scale_apply_scale\m_grph_scale_used_x_scale\p_xscale
      \grph_scale_apply_scale\m_grph_scale_used_y_scale\p_yscale
      \global\d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size
      \global\d_grph_scale_used_y_size\m_grph_scale_used_y_scale\d_grph_scale_y_size}%
     {\grph_scale_calculate_scales\d_grph_scale_used_x_size\d_grph_scale_x_size
      \d_grph_scale_used_y_size\m_grph_scale_used_x_scale\d_grph_scale_y_size}%
     {\grph_scale_calculate_scales\d_grph_scale_used_y_size\d_grph_scale_y_size
      \d_grph_scale_used_x_size\m_grph_scale_used_x_scale\d_grph_scale_x_size}}

\def\grph_scale_by_dimension_indeed#1#2#3%
  {#1\relax
   \ifempty\p_maxwidth\orelse\ifdim\d_grph_scale_used_x_size>\p_maxwidth\relax
     \global\d_grph_scale_used_x_size\p_maxwidth
     #2\relax
   \fi
   \ifempty\p_maxheight\orelse\ifdim\d_grph_scale_used_y_size>\p_maxheight\relax
     \global\d_grph_scale_used_y_size\p_maxheight
      #3\relax
   \fi}

% we can use \lastnamedcs if we want to squeeze out some more

\def\grph_scale_calculate_norm#1#2% todo: swap 1 and 2 and pass one less
  {\csname\??scalenorm\ifcsname\??scalenorm#2\endcsname#2\else\s!unknown\fi\endcsname#1#2}

\def\grph_scale_calculate_fact#1%
  {\csname\??scalefact\ifcsname\??scalefact#1\endcsname#1\else\s!unknown\fi\endcsname}

\defcsname\??scalenorm\v!max    \endcsname#1#-#-#2#-{\global#1{#2}}
\defcsname\??scalenorm\v!fit    \endcsname#1#-#-#-#2{\global#1{#2}}
\defcsname\??scalenorm\v!broad  \endcsname#1#-#-#-#2{\global#1{#2-(\externalfigureparameter\c!bodyfont)*4}} % todo
\defcsname\??scalenorm\s!unknown\endcsname#1#2#-#-#-{\global#1{#2\dimexpr\externalfigureparameter\c!bodyfont/10\relax}} % brr ex
\defcsname\??scalenorm\v!auto   \endcsname#1#-#2#-#-{\ifempty#2\else\global#1{#2}\fi}
\defcsname\??scalenorm\empty    \endcsname#1#-#2#-#-{\ifempty#2\else\global#1{#2}\fi}
\defcsname\??scalenorm\s!default\endcsname#1#-#2#-#-{\ifempty#2\else\global#1{#2}\fi}

\defcsname\??scalefact\v!min    \endcsname{\global\c_grph_scale_swap_factor\conditionaltrue}
\defcsname\??scalefact\s!unknown\endcsname{\global\c_grph_scale_swap_factor\conditionalfalse}

\defcsname\??scalenorm\v!min\endcsname#-#-#-#-#-% an ugly hack
  {\d_grph_scale_used_x_size\hsize
   \d_grph_scale_used_y_size\vsize}

\def\grph_scale_calculate_scales#1#2%
  {\edef\m_grph_scale_used_x_scale{\luaexpr{\number#1/\number#2}}%
   \let\m_grph_scale_used_y_scale\m_grph_scale_used_x_scale}

\def\grph_scale_calculate_scale#1#2#3%
  {\edef#1{\luaexpr{\number#2/\number#3}}}

\def\grph_scale_apply_scale#1#2% #1 = parameter / scale can be empty
  {% no overflow
   \edef#1{\luaexpr
     {\number
      \ifempty#2%
        \ifempty\p_scale            \plusthousand \else
        \ifnum  \p_scale=\zerocount \plusthousand \else
                                    \p_scale      \fi\fi
      \orelse\ifnum#2=\zerocount
        \ifempty\p_scale            \plusthousand \else
        \ifnum  \p_scale=\zerocount \plusthousand \else
                                    \p_scale      \fi\fi
      \else
                                    #2%
      \fi
      /1000}}}

\def\grph_scale_apply_size
  {\ifempty\p_maxheight
     \d_grph_scale_outer_v_size\textheight
     \ifinner
       \d_grph_scale_outer_v_size \vsize % \textheight =\vsize
       \scratchdimen\vsize % \scratchdimen=\textheight
     \orelse\ifinsidefloat
       \d_grph_scale_outer_v_size \vsize % \textheight =\vsize
       \scratchdimen\vsize % \scratchdimen=\textheight
     \orelse\ifinpagebody
       \d_grph_scale_outer_v_size \vsize % \textheight =\vsize
       \scratchdimen\vsize % \scratchdimen=\textheight
     \orelse\ifdim\pagegoal<\maxdimen
       \ifdim\pagetotal<\pagegoal
         \scratchdimen{\pagegoal-\pagetotal}%
       \else
         \scratchdimen\d_grph_scale_outer_v_size % \textheight
       \fi
     \else
       \scratchdimen\d_grph_scale_outer_v_size % \textheight
     \fi
   \else
     \scratchdimen\p_maxheight
     \d_grph_scale_outer_v_size\scratchdimen
   \fi
   \ifempty\p_height
     \d_grph_scale_v_size\scratchdimen
   \else
     \d_grph_scale_v_size{\p_height}%
   \fi
   \ifempty\p_width
     \d_grph_scale_h_size\hsize
   \else
     \d_grph_scale_h_size{\p_width}%
   \fi}

% \startcombination
%     {\externalfigure[cow.pdf] [frame=on,height=3cm,equalwidth=6cm]} {a cow}
%     {\externalfigure[mill.png][frame=on,height=3cm,equalwidth=6cm]} {a mill}
% \stopcombination

\def\grph_scale_position
  {\ifempty\p_equalwidth \else
    \scratchdimen\p_equalwidth\relax
    \ifdim\d_grph_scale_wd<\scratchdimen
      \setbox\nextbox\naturalhpack to \scratchdimen{\hss\box\nextbox\hss}%
    \fi
   \fi
   \ifempty\p_equalheight \else
     \scratchdimen\p_equalheight\relax
     \ifdim\d_grph_scale_ht<\scratchdimen
       \setbox\nextbox\naturalvpack to \scratchdimen{\vss\box\nextbox\vss}%
     \fi
   \fi}

\permanent\protected\def\fastscale#1%
  {\ifnum#1=1000\relax
     \expandafter\grph_scale_fast_nop
   \else
     \expandafter\grph_scale_fast_yes
   \fi{#1}}

\def\grph_scale_fast_nop#1%
  {\hbox}

\def\grph_scale_fast_yes#1%
  {\edef\finalscaleboxxscale{\toscaled\dimexpr#1\onepoint/1000\relax}% brrr
   \let\finalscaleboxyscale\finalscaleboxxscale
   \dowithnextboxcs\grph_scale_fast_finish\hbox} % container ?

\def\grph_scale_fast_finish
  {\grph_scale_apply
   \box\nextbox
   \endgroup}

\permanent\protected\def\fastsxsy#1#2%
  {\bgroup
   \edef\p_sx{#1}%
   \edef\p_sy{#2}%
   \dowithnextboxcs\grph_scale_fast_sx_xy_finish\hbox} % container?

\def\grph_scale_fast_sx_xy_finish
  {\grph_scale_check_sx_sy
   \d_grph_scale_wd\wd\nextbox
   \d_grph_scale_ht\ht\nextbox
   \d_grph_scale_dp\dp\nextbox
   \grph_scale_calculations_nop
   \grph_scale_apply
   \box\nextbox
   \egroup}

%D \macros
%D   {clip, setupclipping}
%D
%D Although related to figures, clipping can be applied to arbitrary content. We can
%D use \METAPOST\ to provide a non rectangular clipping path.
%D
%D \starttyping
%D \startMPclip{fun}
%D   clip currentpicture to fullcircle
%D     shifted (.5,.5) xscaled \width yscaled \height ;
%D \stopMPclip
%D \stoptyping
%D
%D We get a rectangular piece of the figure when we say:
%D
%D \starttyping
%D \clip[nx=4,x=2,y=1]{\externalfigure[photo]}
%D \stoptyping
%D
%D When we want to clip to the oval we defined a few lines ago, we say:
%D
%D \starttyping
%D \clip[nx=1,ny=1,x=1,y=1,mp=fun]{\externalfigure[photo]}
%D \stoptyping
%D
%D The general characteristics of clipping can be set up with
%D
%D \showsetup{setupclipping}

\installcorenamespace{clipping}

\installdirectcommandhandler \??clipping {clipping}

\permanent\tolerant\protected\def\clip[#S#1]% nb top->bottom left->right
  {\bgroup
   \ifparameter#1\or
     \setupcurrentclipping[#1]%
   \fi
   \dowithnextboxcs\grph_clip_finish\hbox}

\def\grph_clip_finish
  {\ifcstok{\clippingparameter\c!state}\v!start
     \grph_clip_yes_finish
   \else
     \grph_clip_nop_finish
   \fi}

\def\grph_clip_yes_finish
  {\scratchwidth{\clippingparameter\c!width}%
   \ifdim\scratchwidth>\zeropoint
     \scratchxoffset{\clippingparameter\c!hoffset}%
   \else
     \scratchwidth{\wd\nextbox/(\clippingparameter\c!nx)}%
     \scratchxoffset{\scratchwidth*(\clippingparameter\c!x)-\scratchwidth}%
     \scratchwidth\clippingparameter\c!sx\scratchwidth
   \fi
   \relax % sure
   \scratchheight{\clippingparameter\c!height}%
   \ifdim\scratchheight>\zeropoint
     \scratchyoffset{\ht\nextbox-(\clippingparameter\c!voffset)-\scratchheight}%
   \else
     \scratchheight{\ht\nextbox/(\clippingparameter\c!ny)}%
     \scratchyoffset{-\scratchheight*(\clippingparameter\c!y)-\clippingparameter\c!sy\scratchheight+\scratchheight}%
     \scratchheight\clippingparameter\c!sy\scratchheight
     \advanceby\scratchyoffset\ht\nextbox
   \fi
   \scratchleftoffset  {\clippingparameter\c!leftoffset}%
   \scratchrightoffset {\clippingparameter\c!rightoffset}%
   \scratchtopoffset   {\clippingparameter\c!topoffset}%
   \scratchbottomoffset{\clippingparameter\c!bottomoffset}%
   \setbox\nextbox\naturalhpack
     {\advanceby\scratchxoffset-\scratchleftoffset
      \advanceby\scratchyoffset-\scratchbottomoffset
      \hskip-\scratchxoffset
      \lower\scratchyoffset
      \box\nextbox}%
   \wd\nextbox\zeropoint
   \ht\nextbox\zeropoint
   \dp\nextbox\zeropoint
   \setbox\nextbox\naturalhpack
     {\advanceby\scratchwidth {\scratchleftoffset  +\scratchrightoffset}%
      \advanceby\scratchheight{\scratchbottomoffset+\scratchtopoffset}%
      \dostartclipping{\clippingparameter\c!mp}\scratchwidth\scratchheight
        \box\nextbox
      \dostopclipping}%
   \setbox\nextbox\hcontainer
     {\hskip-\scratchleftoffset
      \lower \scratchbottomoffset
      \box\nextbox}%
   \wd\nextbox\scratchwidth
   \ht\nextbox\scratchheight
   \dp\nextbox\zeropoint
   \box\nextbox
   \egroup}

\def\grph_clip_nop_finish
  {\box\nextbox
   \egroup}

\setupclipping
  [\c!state=\v!start,
   \c!n=\plusone, % was \plustwo
   \c!nx=\clippingparameter\c!n,\c!x=\plusone,\c!sx=\plusone,
   \c!ny=\clippingparameter\c!n,\c!y=\plusone,\c!sy=\plusone,
   \c!width=\zeropoint,
   \c!height=\zeropoint,
   \c!hoffset=\zeropoint,
   \c!voffset=\zeropoint,
   \c!offset=\zeropoint,
   \c!leftoffset=\clippingparameter\c!offset,
   \c!rightoffset=\clippingparameter\c!offset,
   \c!topoffset=\clippingparameter\c!offset,
   \c!bottomoffset=\clippingparameter\c!offset,
   \c!mp=]

%D \startbuffer
%D \startuseMPgraphic{test}
%D   path p ; p := fullcircle scaled 4cm ;
%D   draw p withpen pencircle scaled 1cm ;
%D   setbounds currentpicture to boundingbox p ;
%D \stopuseMPgraphic
%D
%D \hbox to \hsize \bgroup
%D   \hss
%D   \ruledhbox{\useMPgraphic{test}}%
%D   \hss
%D   \ruledhbox{\clip{\useMPgraphic{test}}}%
%D   \hss
%D \egroup
%D \stopbuffer
%D
%D \typebuffer \getbuffer

%D Mirroring.

\permanent\protected\def\mirror
  {\bgroup
   \dowithnextboxcs\grph_mirror_finish\hbox}

\def\grph_mirror_finish
  {\scratchdimen\wd\nextbox
   % better use an hbox (if no \forgetall, leftskip etc may creep in)
   %\setbox\nextbox\vbox{\forgetall\dostartmirroring\hskip-\wd\nextbox\box\nextbox\dostopmirroring}%
   \setbox\nextbox\naturalhpack
     {\dostartmirroring
      \hskip-\wd\nextbox
      \box\nextbox
      \dostopmirroring}%
   \wd\nextbox\scratchdimen
   \box\nextbox
   \egroup}

%D A couple of examples, demonstrating how the depth is taken care of:
%D
%D \startbuffer
%D test\rotate[frame=on, rotation=0]  {gans}%
%D test\rotate[frame=on, rotation=90] {gans}%
%D test\rotate[frame=on, rotation=180]{gans}%
%D test\rotate[frame=on, rotation=270]{gans}%
%D test
%D \stopbuffer
%D
%D \typebuffer \getbuffer
%D
%D When we rotate over arbitrary angles, we need to relocate the resulting box
%D because rotation brings that box onto the negative axis. The calculations (mostly
%D sin and cosine) need to be tuned for the way a box is packages (i.e. the refence
%D point). A typical example of drawing, scribbling, and going back to the days of
%D school math.
%D
%D We do a bit more calculations than needed, simply because that way it's easier to
%D debug the code.

\installcorenamespace {rotate}
\installcorenamespace {rotatelocation}
\installcorenamespace {rotatepreset}

%D These are set at the \LUA\ end (it's a cheaper operation than setting a dimen
%D register and these are actually kind of constants not used in further
%D calculations):

% mutable\dimendef\d_grph_rotate_x_size    \zeropoint
% mutable\dimendef\d_grph_rotate_y_size    \zeropoint
% mutable\dimendef\d_grph_rotate_x_offset  \zeropoint
% mutable\dimendef\d_grph_rotate_y_offset  \zeropoint
% mutable\dimendef\d_grph_rotate_x_position\zeropoint
% mutable\dimendef\d_grph_rotate_y_position\zeropoint
%
% mutable\dimendef\d_grph_rotate_new_width \zeropoint
% mutable\dimendef\d_grph_rotate_new_height\zeropoint
% mutable\dimendef\d_grph_rotate_new_depth \zeropoint

%D These aren't:

\newdimension\d_grph_rotate_used_height

\newdimension\d_grph_rotate_width
\newdimension\d_grph_rotate_height
\newdimension\d_grph_rotate_depth

\newdimension\d_grph_rotate_saved_width
\newdimension\d_grph_rotate_saved_height
\newdimension\d_grph_rotate_saved_depth

\newconditional\c_grph_rotate_obey_depth
\newconditional\c_grph_rotate_not_fit
\newconditional\c_grph_rotate_center

\installframedcommandhandler \??rotate {rotate} \??rotate

\setuprotate
  [\c!rotation=90,
   \c!location=\v!normal,
   \c!width=\v!fit,
   \c!height=\v!fit,
   \c!offset=\v!overlay,
   \c!frame=\v!off]

\lettonothing\p_rotation_location
\lettonothing\p_rotation_rotation

\permanent\tolerant\protected\def\rotate[#S#1]% \bgroup: \rotate kan argument zijn
  {\bgroup
   \ifparameter#1\or
     \setupcurrentrotate[#1]%
   \fi
   \edef\p_rotation_location{\rotateparameter\c!location}%
   \edef\p_rotation_rotation{\rotateparameter\c!rotation}%
   \ifcsname\??rotatelocation\p_rotation_location\endcsname
      \expandafter\lastnamedcs
   \else
      \expandafter\grph_rotate_default
   \fi}

\def\grph_rotate_framed
  {\resetrotateparameter\c!location
   \dowithnextboxcs\grph_rotate_finish\vbox
   \inheritedrotateframed}

\def\grph_rotate_normal
  {\dowithnextboxcs\grph_rotate_finish\vbox}

\def\grph_rotate_finish
  {\grph_rotate_finish_indeed
   \egroup}

\def\grph_rotate_default
  {\c_grph_rotate_not_fit\conditionalfalse
   \c_grph_rotate_center\conditionalfalse
   \c_grph_rotate_obey_depth\conditionaltrue
   \grph_rotate_framed}

\letcsname\??rotatelocation\v!default\endcsname\grph_rotate_default

\defcsname\??rotatelocation\v!depth\endcsname
  {\c_grph_rotate_not_fit\conditionalfalse
   \c_grph_rotate_center\conditionalfalse
   \c_grph_rotate_obey_depth\conditionaltrue
   \grph_rotate_normal}

\defcsname\??rotatelocation\v!fit\endcsname
  {\c_grph_rotate_not_fit\conditionaltrue
   \c_grph_rotate_center\conditionalfalse
   \c_grph_rotate_obey_depth\conditionaltrue
   \grph_rotate_normal}

\defcsname\??rotatelocation\v!broad\endcsname
  {\c_grph_rotate_not_fit\conditionalfalse
   \c_grph_rotate_center\conditionalfalse
   \c_grph_rotate_obey_depth\conditionalfalse
   \grph_rotate_normal}

\defcsname\??rotatelocation\v!high\endcsname
  {\c_grph_rotate_not_fit\conditionalfalse
   \c_grph_rotate_center\conditionalfalse
   \c_grph_rotate_obey_depth\conditionalfalse
   \grph_rotate_framed}

\defcsname\??rotatelocation\v!middle\endcsname
  {\c_grph_rotate_not_fit\conditionalfalse
   \c_grph_rotate_center\conditionaltrue
   \c_grph_rotate_obey_depth\conditionalfalse  % hm, depth ?
   \grph_rotate_normal}

\permanent\protected\def\dorotatebox#1% {angle} \hbox/\vbox/\vtop % a fast low level one
  {\ifcase#1\relax
     \expandafter\gobbleoneargument
   \else
     \expandafter\grph_rotate_box
   \fi{#1}}

\def\grph_rotate_box#1% {angle} \hbox/\vbox/\vtop
  {\bgroup
   \hbox\bgroup % compatibility hack
     \edef\p_rotation_rotation{#1}%
     \dowithnextboxcs\grph_rotate_finish_box}

\def\grph_rotate_finish_box
  {\c_grph_rotate_not_fit\conditionalfalse      % this is the same as broad but
   \c_grph_rotate_center\conditionalfalse       % without the following grab as
   \c_grph_rotate_obey_depth\conditionalfalse   % we call finish directly
   \grph_rotate_finish_indeed
   \egroup
   \egroup}

\def\grph_rotate_finish_indeed
  {\naturalhpack\bgroup
     \ifempty\p_rotation_rotation
       \grph_rotate_finish_nop
     \else
       \grph_rotate_finish_yes
     \fi
   \egroup}

\def\grph_rotate_finish_nop
  {\boxcursor\box\nextbox}

\defcsname\??rotatepreset\v!left\endcsname
  {\edef\p_rotation_rotation{90}}

\defcsname\??rotatepreset\v!right\endcsname
  {\edef\p_rotation_rotation{270}}

\defcsname\??rotatepreset\v!inner\endcsname
  {\signalrightpage
   \doifelserightpage{\def\p_rotation_rotation{270}}{\def\p_rotation_rotation{90}}}

\defcsname\??rotatepreset\v!outer\endcsname
  {\signalrightpage
   \doifelserightpage{\def\p_rotation_rotation{90}}{\def\p_rotation_rotation{270}}}

\letcsname\??rotatepreset\v!default\endcsname\empty

\def\grph_rotate_finish_yes
  {\begincsname\??rotatepreset\p_rotation_rotation\endcsname
   \setbox\nextbox\naturalvpack{\box\nextbox}% not really needed
   \dontcomplain
   \ifconditional\c_grph_rotate_center
     \d_grph_rotate_saved_width \wd\nextbox
     \d_grph_rotate_saved_height\ht\nextbox
     \d_grph_rotate_saved_depth \dp\nextbox
     \setbox\nextbox\naturalhpack{\hskip-.5\wd\nextbox\lower.5\ht\nextbox\box\nextbox}%
     \smashbox\nextbox
   \fi
   %
   \d_grph_rotate_width \wd\nextbox
   \d_grph_rotate_height\ht\nextbox
   \d_grph_rotate_depth \dp\nextbox
   %
   \setbox\nextbox\naturalvpack{\naturalhpack{\raise\dp\nextbox\box\nextbox}}% can we do without
   %
   \d_grph_rotate_used_height\ht\nextbox
   %
   \clf_analyzerotate % rather accurate
     \p_rotation_rotation\relaxedspace
     \d_grph_rotate_width
     \d_grph_rotate_height
     \d_grph_rotate_depth
     \d_grph_rotate_used_height
     \c_grph_rotate_not_fit
     \c_grph_rotate_obey_depth
   \relax
   %
   \setbox\nextbox\naturalvpack to \d_grph_rotate_y_size
     {\vfilll
      \hcontainer to \d_grph_rotate_x_size
        {\dostartrotation\p_rotation_rotation
           \wd\nextbox\zeropoint
           \ht\nextbox\zeropoint
           \box\nextbox
         \dostoprotation
         \hfill}%
      \kern\d_grph_rotate_y_position}%
   %
   \setbox\nextbox\naturalhpack
     {\kern{\d_grph_rotate_x_position+\d_grph_rotate_x_offset}%
      \lower\d_grph_rotate_y_offset
      \box\nextbox}%
   %
   \ifconditional\c_grph_rotate_center
     \setbox\nextbox\naturalhpack{\hskip.5\d_grph_rotate_saved_width\lower-.5\d_grph_rotate_saved_height\box\nextbox}%
     \wd\nextbox\d_grph_rotate_saved_width
     \ht\nextbox\d_grph_rotate_saved_height
     \dp\nextbox\d_grph_rotate_saved_depth
   \else
     \wd\nextbox\d_grph_rotate_new_width
     \ht\nextbox\d_grph_rotate_new_height
     \dp\nextbox\d_grph_rotate_new_depth
   \fi
   %
   \boxcursor\box\nextbox}

% \dostepwiserecurse{0}{360}{10}
%   {\startlinecorrection[blank]
%    \hbox
%      {\setuprotate[rotation=#1]%
%       \traceboxplacementtrue
%       \hbox to .2\hsize{\hss\ruledhbox{\rotate[location=depth] {\ruledhbox{\bfb  (depth)}}}}%
%       \hbox to .2\hsize{\hss\ruledhbox{\rotate[location=fit]   {\ruledhbox{\bfb    (fit)}}}}%
%       \hbox to .2\hsize{\hss\ruledhbox{\rotate[location=broad] {\ruledhbox{\bfb  (broad)}}}}%
%       \hbox to .2\hsize{\hss\ruledhbox{\rotate[location=normal]{\ruledhbox{\bfb (normal)}}}}%
%       \hbox to .2\hsize{\hss\ruledhbox{\rotate[location=high]  {\ruledhbox{\bfb   (high)}}}}}
%    \stoplinecorrection}

% \def\Test{\ruledhbox{%
%     \def\DemoX{\vl\kern.5\emwidth\vl}%
%     \kern\emwidth\ruledhpack{\green\rotate[rotation=20]  {\ruledhpack{\DemoX}}}%
%     \kern\emwidth\ruledhpack{\blue \rotate[rotation=0]   {\ruledhpack{\DemoX}}}%
%     \kern\emwidth\ruledhpack{\red  \rotate[rotation=-20] {\ruledhpack{\DemoX}}}%
%     \kern\emwidth\ruledhpack{\green\rotate[rotation=200] {\ruledhpack{\DemoX}}}%
%     \kern\emwidth\ruledhpack{\blue \rotate[rotation=180] {\ruledhpack{\DemoX}}}%
%     \kern\emwidth\ruledhpack{\red  \rotate[rotation=-200]{\ruledhpack{\DemoX}}}%
%     \kern\emwidth}}

% \startTEXpage[offset=10pt,align=middle]
%     \setuprotate[location=fit]     \Test \par {\infofont\setstrut\strut fit}     \par
%     \setuprotate[location=depth]   \Test \par {\infofont\setstrut\strut depth}   \par
%     \setuprotate[location=broad]   \Test \par {\infofont\setstrut\strut broad}   \par
%     \setuprotate[location=high]    \Test \par {\infofont\setstrut\strut high}    \par
%     \setuprotate[location=middle]  \Test \par {\infofont\setstrut\strut middle}  \par
%     \setuprotate[location=default] \Test \par {\infofont\setstrut\strut default} \par
% \stopTEXpage

%D Something new (okay, actually old) but very \PDF\ dependent:

\def\grph_with_group
  {\d_grph_scale_wd\wd\nextbox
   \d_grph_scale_ht\ht\nextbox
   \d_grph_scale_dp\dp\nextbox
   \setbox\nextbox\hcontainer
     {\clf_startgraphicgroup
      \smashedbox\nextbox
      \clf_stopgraphicgroup}%
   \wd\nextbox\d_grph_scale_wd
   \ht\nextbox\d_grph_scale_ht
   \dp\nextbox\d_grph_scale_dp
   \box\nextbox}

\permanent\protected\def\startgraphicgroup
  {\dontleavehmode
   \begingroup
   \dowithnextboxcs\grph_with_group
   \hbox\bgroup}

\permanent\protected\def\stopgraphicgroup
  {\egroup
   \endgroup}

%D This is used in:

\permanent\protected\def\startclipeffect
   {\begingroup
    \dowithnextbox
      {\stopeffect
       \setbox\scratchboxone\box\nextbox
       \dowithnextbox
         {\setbox\scratchboxtwo\box\nextbox
          \startgraphicgroup
          \startoverlay
             {\box\scratchboxone}%
             {\box\scratchboxtwo}%
          \stopoverlay
          \stopgraphicgroup}
       \hbox}%
    \hbox\bgroup\starteffect[\v!clip]\let\next}

\permanent\protected\def\stopclipeffect
  {\endgroup}

% \setbox0\hbox{\strut\starteffect[clip]mikael\stopeffect}
%
% \startgraphicgroup
%     \startoverlay
%       {\copy0}
%       {\externalfigure[hacker.jpg][width=\wd0,height=\lineheight]}
%     \stopoverlay
% \stopgraphicgroup
%
% \startclipeffect
%     {\hbox to 3cm{\hss\strut mikael\hss}}
%     {\externalfigure[hacker.jpg][width=3cm,height=1cm]}
% \stopclipeffect
%
% \defineoverlay
%   [hacker]
%   [\overlayfigure{hacker.jpg}]
%
% \startgraphicgroup
% \framed
%   [background={foreground,hacker},align={middle,lohi},width=8cm]
%   {\starteffect[clip]\samplefile{tufte}\stopeffect}
% \stopgraphicgroup

\protect \endinput
