friedemannbartels / latex-pagelayout

Declarative Page Layouts
LaTeX Project Public License v1.3c
33 stars 0 forks source link

Help for "complex" layouts #4

Open francofusco opened 3 months ago

francofusco commented 3 months ago

Hello! Thanks for the class, it's very nice and intuitive to use!

I am experimenting with it to see if I can create some travel pohotobooks where the layouts are a bit more "playful" than what appears in professional photobooks. As an example, my current "challenge" is to create a layout that contains 5 cells as follows (for a "lay-flat" kind of book):

layout

The "specs" on the sizes are as follows:

What I've achieved so far is ok visually, but a bit hacky in the way it is coded. If possible, I need a bit of guidance to clarify some doubts and to improve the solution I came up with.

This is what I got. First of all, I created a "backup" of the lengths \bleed, \margin and \gutter, saving them in the lengths \documentbleed, \documentmargin and \documentgutter. I then created a template with \newtemplate[double, margin=-\documentbleed, gutter=0pt]{complex}{...}. The reason for margin=-\documentbleed is that the "drawing area" should span the whole page, bleed area included. The reason for gutter=0pt... is actually not entirely clear to me, I'll get back to it later in the description. With the page setup, I then used pgfmath to calculate manually all dimensions and positions needed, then I used \placeholder{y1 x1 y2 x2} to allocate the elements. Here is the code for the template:

\newtemplate[double, margin=-\documentbleed, gutter=0pt]{complex}{
  % Dimensions of the drawing area.
  \pgfmathsetmacro{\drawablewidth}{2 * \pagewidth + 2 * \documentbleed}
  \pgfmathsetmacro{\drawableheight}{\pageheight + 2 * \documentbleed}

  % Width and height of the corner pictures.
  \pgfmathsetmacro{\picwidth}{(\drawablewidth - 2 * \documentbleed - 2 * \documentmargin - 2 * \documentgutter) / 3}
  \pgfmathsetmacro{\picheight}{(\drawableheight - 2 * \documentbleed - 2 * \documentmargin - \documentgutter) / 2}

  % Place the first picture in the top-left corner.
  \pgfmathsetmacro{\rowstart}{(\documentbleed + \documentmargin) / \drawableheight}
  \pgfmathsetmacro{\rowend}{\rowstart + \picheight / \drawableheight}
  \pgfmathsetmacro{\colstart}{(\documentbleed + \documentmargin) / \drawablewidth}
  \pgfmathsetmacro{\colend}{\colstart + \picwidth / \drawablewidth}
  \placeholder{{\rowstart} {\colstart} {\rowend} {\colend}}

  % Place the second picture in the bottom-left corner.
  \pgfmathsetmacro{\rowstart}{(\documentbleed + \documentmargin + \picheight + \documentgutter) / \drawableheight}
  \pgfmathsetmacro{\rowend}{\rowstart + \picheight / \drawableheight}
  \pgfmathsetmacro{\colstart}{(\documentbleed + \documentmargin) / \drawablewidth}
  \pgfmathsetmacro{\colend}{\colstart + \picwidth / \drawablewidth}
  \placeholder{{\rowstart} {\colstart} {\rowend} {\colend}}

  % Place the third picture in the middle.
  \pgfmathsetmacro{\colstart}{(\documentbleed + \documentmargin + \picwidth + \documentgutter) / \drawablewidth}
  \pgfmathsetmacro{\colend}{\colstart + \picwidth / \drawablewidth}
  \placeholder{0 {\colstart} 1 {\colend}}

  % Place the fourth picture in the top-right corner.
  \pgfmathsetmacro{\rowstart}{(\documentbleed + \documentmargin) / \drawableheight}
  \pgfmathsetmacro{\rowend}{\rowstart + \picheight / \drawableheight}
  \pgfmathsetmacro{\colstart}{(\documentbleed + \documentmargin + 2 * \documentgutter + 2 * \picwidth) / \drawablewidth}
  \pgfmathsetmacro{\colend}{\colstart + \picwidth / \drawablewidth}
  \placeholder{{\rowstart} {\colstart} {\rowend} {\colend}}

  % Place the fifth picture in the bottom-right corner.
  \pgfmathsetmacro{\rowstart}{(\documentbleed + \documentmargin + \picheight + \documentgutter) / \drawableheight}
  \pgfmathsetmacro{\rowend}{\rowstart + \picheight / \drawableheight}
  \pgfmathsetmacro{\colstart}{(\documentbleed + \documentmargin + 2 * \documentgutter + 2 * \picwidth) / \drawablewidth}
  \pgfmathsetmacro{\colend}{\colstart + \picwidth / \drawablewidth}
  \placeholder{{\rowstart} {\colstart} {\rowend} {\colend}}
}

The code above works, but it has a limitation: to change the margin and distance between pictures, it is necessary to do manually change the "back-up lengths" as in:

\begingroup
\setlength{\documentmargin}{5mm}
\setlength{\documentgutter}{5mm}
\template{complex}{
    \graphic{img1}
    \graphic{img2}
    \graphic{img3}
    \graphic{img4}
    \graphic{img5}
}
\endgroup

which is breaking the (clear and simple) API used in \template[]{} - where you would simply set the values using the keyword arguments.

I've tried a different approach that does not need to use documentmargin & friends, but it appears to be broken. More precisely, I create a page without changing the margin, and then position everything relative to the drawing area - which is the page, minus the bleed and the margin. The corner images are placed with ease, but when I try to position the center image something goes wrong. The code I need to execute is:

% Start in a negative row - since we need to reach the top margin of the page.
\pgfmathsetmacro{\rowstart}{-(\margin + \bleed) / \drawableheight}

% End at the bottom of the page. Since \rowstart is negative, \rowend is larger than 1.
\pgfmathsetmacro{\rowend}{1 - \rowstart} % THIS DOES NOT WORK

% The starting and ending columns are calculated relative to the drawing area similarly to what is done in the working version of the template.
\pgfmathsetmacro{\colstart}{(\picwidth+\gutter) / \drawablewidth}

% Place the object.
\placeholder{{\rowstart} {\colstart} {\rowend} {\colend}}

The code above produces the following spread:

broken-layout

A simplified version of the failing code (which places only the center image using a hard-coded width) is the following:

\newtemplate[double]{broken}{
    \pgfmathsetmacro{\drawablewidth}{2 * \pagewidth - 2 * \bleed - 2 * \margin}
    \pgfmathsetmacro{\drawableheight}{\pageheight - 2 * \bleed - 2 * \margin}

    \pgfmathsetmacro{\rowstart}{-(\margin + \bleed) / \drawableheight}
    \pgfmathsetmacro{\rowend}{1 - \rowstart} % THIS DOES NOT WORK
    \placeholder{{\rowstart} 0.25 {\rowend} 0.75}
}

Note that if I set \rowend manually to 1, then the image is placed as expected (despite the negative initial row):

semibroken-layout

Any idea how to fix this?

I also have a couple of random doubts:

Thanks in advance for any help that can be given!

francofusco commented 3 months ago

Small update regarding my last question: I found a way to create templates that contain parameters that can be declared as key-value pairs. The values can be specified when "using" the templates later on.

Here is an example of usage:

% Declare a template that has the two parameters "hratio" and "vratio", whose
% default value is 0.5 (for both).
\makeatletter
\NewParametricTemplate{FlexibleWindows}{hratio=0.5, vratio=0.5}{
    \placeholder{0 0 {\FlexibleWindows@vratio} {\FlexibleWindows@hratio}}
    \placeholder{0 {\FlexibleWindows@hratio} {\FlexibleWindows@vratio} 1}
    \placeholder{{\FlexibleWindows@vratio} 0 1 {\FlexibleWindows@hratio}}
    \placeholder{{\FlexibleWindows@vratio} {\FlexibleWindows@hratio} 1 1}
}
\makeatother

\begin{document}

% Use the template by changing the parameters.
\ParametricTemplate[hratio=0.6, vratio=0.4]{FlexibleWindows}{
    \graphic{img1}
    \graphic{img2}
    \graphic{img3}
    \graphic{img4}
}

% Use the template by changing the parameters. We can also change margins & co.
\ParametricTemplate[hratio=0.4, vratio=0.4][margin=5mm, gutter=1mm]{FlexibleWindows}{
    \graphic{img1}
    \graphic{img2}
    \graphic{img3}
    \graphic{img4}
}

\end{document}

which produces the following pages:

parametric-templates

For those interested, this is the code for the \NewParametricTemplate and \ParametricTemplate commands:

\usepackage{kvoptions}
\usepackage{xparse}

\makeatletter%
% Initialize the given key with the given value. To be used with \kv@parse to
% declare a list of key-value pairs.
% Usage:
%   \ProcessorParseAndDeclare{key}{value}
\newcommand{\ProcessorParseAndDeclare}[2]{\DeclareStringOption[#2]{#1}}

% Create a new template that accepts parameters in the form of key-value pairs.
% The command declares the key-value pairs (using the template name as family)
% and then calls the \template command from the pagelayout class.
% Usage:
%   \NewParametricTemplate[pagelayout-kvoptions]{name}{kvparameters}{body}
%
% Args:
%   - pagelayout-kvoptions: Key-value options to be forwarded to the \template
%       command as its optional arguments (example: "margin=3mm, gutter=5pt").
%   - name: Name of the template. This must not contain spaces because this name
%       is used to prefix the parameters using the kvoptions package.
%   - kvparameters: Key-value parameters that can be used in the template body.
%       The values passed in the list are used as the defaults for the keys.
%   - body: Code to be passed as the last parameter to the \template command.
%       Parameters are accessible in the body as "\name@param".
%
% Example:
%   \makeatletter
%   \NewParametricTemplate{SimpleParametric}{ratio=0.5}{
    %       \placeholder{0 0 1 {\SimpleParametric@ratio}}
    %       \placeholder{0 {\SimpleParametric@ratio} 1 1}
    %   }
%   \makeatother
%
% Note: as shown in the example, it is very important to wrap the declaration
% within the "\makeatletter" and "\makeatother" commands. Otherwise, the full
% parameter names (that is, \SimpleParametric@ratio in the example) are not
% expanded as intended.
\NewDocumentCommand{\NewParametricTemplate}{O{} m m m}{%
    \SetupKeyvalOptions{family=#2, prefix=#2@}%
    \kv@parse{#3}{\ProcessorParseAndDeclare}%
    \ProcessKeyvalOptions{#2}%
    \newtemplate[#1]{#2}{#4}%
}

% Use a template declared with \NewParametricTemplate.
% Usage:
%   \ParametricTemplate[kvparameters][pagelayout-kvoptions]{name}{content}
%
% Args:
%   - kvparameters: Key-value pairs to set one or more parameters of the
%       template. Every parameter that is not set explicitly here will use the
%       default value specified during declaration in \NewParametricTemplate.
%   - pagelayout-kvoptions: Key-value options to be forwarded to the \template
%       command as its optional arguments (example: "margin=3mm, gutter=5pt").
%   - name: Name of the template to be used - must match the name of one of the
%       templates created with \NewParametricTemplate.
%   - content: Mix of graphics and text to fill the template. This is equivalent
%       to what would be passed as last argument to \template.
%
% Examples:
%   \ParametricTemplate{SimpleParametric}{\graphic{img1}\graphic{img2}}
%   \ParametricTemplate[ratio=0.35]{SimpleParametric}{\graphic{img1}\graphic{img2}}
%   \ParametricTemplate[][margin=2cm]{SimpleParametric}{\graphic{img1}\graphic{img2}}
%   \ParametricTemplate[ratio=0.55][gutter=5mm]{SimpleParametric}{\graphic{img1}\graphic{img2}}
\NewDocumentCommand{\ParametricTemplate}{O{} O{} m m}{%
    \kvsetkeys{#3}{#1}%
    \template[#2]{#3}{#4}%
}
\makeatother
friedemannbartels commented 3 months ago

Hi, thank you for your feedback.

Any idea how to fix this?

It is important to understand when working with the grid and the \placeholder or \place command you are dealing with relative values to place content boxes inside the grid. In your example you mixed these relative values (eg. 0.5) with absolute dimensions like the gutter (eg. 5mm). This is considered a wrong approach. The grid takes care to place content boxes with the current gutter automatically without needing to change the relative values. For complex layouts, you should use multiple grids. You can position a grid with absolute dimensions for x, y, width, height. And you can do calculations when setting these dimensions. Here is an example of how you can implement your layout with multiple grids:

\newtemplate[double]{complex}{
  \setgrid[width=(\width-2\gutter)/3, x=\margin]{
    {{}}
    {{}}
  }
  \placeholder{}
  \placeholder{}

  \setgrid[width=(\width-2\gutter)/3, height=\pageheight]{
    {{}}
  }
  \placeholder{}

  \setgrid[width=(\width-2\gutter)/3, x=(\width-2\gutter)/3*2+2\gutter+\margin]{
    {{}}
    {{}}
  }
  \placeholder{}
  \placeholder{}
}

The third placeholder has the height of the page without the bleed. If you fill this placeholder with text, the text is aligned with the vertical page margins. However, if you fill this placeholder with a graphic, the graphic is automatically extended into the bleed.

If you use this template on a double page, make sure to update the pagelayout class to version 1.1.1 as there is a bug related to double pages in prior versions. I just released the update, so it might take up to 24 hours until it is available in your TeX package manager.

Version 1.1.1 contains a test case and a test result for your specific layout on a double page.

I also have a couple of random doubts:

  • Is it correct that the gutter in this package refers to the distance between pictures? In other contexts, I've seen it used to refer to the inner margin of a page, where the pages are bound together.

The gutter refers to the distance between content boxes placed within the grid. You can define the innermargin and outermargin for a document, a page, a template or a grid.

  • Is the gutter applied around images when calling \placeholder? Because when prototyping the template I tried keeping the gutter as it was, and simply use \gutter for spacing images. However, the result was that images were twice as far as I would expect. My conclusion was that the gutter is "applied" around each image and thus I "deactivated" it using gutter=0pt in the template declaration, and resorted to the use of \documentgutter for all calculations.

The gutter is applied between content boxes created with the \placeholder or \place command within a grid. I hope the above explanation and example made this more clear.

  • What would be a simple way to create "parametric templates"? As an example, say that I wanted the width of the middle picture to be a (user-supplied) fraction of the width of the other pictures. How would you suggest to define "extra parameters" that need to be used inside a template?

There is no out of the box way to parametrize templates the way your want it. I think the approch you provided in your second comment is a valid way to go. But depending on the number of variations of the template, I would consider to use multiple templates instead of your parametrized approch.

To give you an inspiration how I would implement your second layout, take a look at this example:

\newtemplate{adaptive}{
  \ifleftpage{
    \setgrid{
      {[4]{}{4!}}
      {[6]{6!}{}}
    }
  }

  \ifrightpage{
    \setgrid{
      {[4]{4!}{}}
      {[6]{}{6!}}
    }
  }

  \placeholder{}
  \placeholder{}
  \placeholder{}
  \placeholder{}
}

I hope you got a better understanding how to utalize this class. If you struggle with other complex layouts, feel free to provide a specification and I am pleased to support you.