4

What I'm trying to do

I am creating some handwriting drill sheets. In these sheets, the exact same pattern is repeated horizontally and vertically over the entire page. The following 3 images were created using Inkscape, each of them represent the content of a single page that I want to create using TikZ.

enter image description here enter image description here enter image description here

What I know

I just learned that I can define a pattern inside \newsavebox.

\documentclass{article}

\usepackage{tikz}

\newsavebox \mybox \sbox \mybox {% \tikz{% \draw (0,0) circle (0.2); \draw (0,0) circle (0.4); \draw (0,0) circle (0.6); \draw (0,0) circle (0.8); \draw (0,0) circle (1); }% }

\begin{document} \begin{tikzpicture} \node at (0, 0) {\usebox\mybox}; \node at (2, 0) {\usebox\mybox}; \end{tikzpicture} \end{document}

enter image description here

I also know that I can use loops to repeat the same pattern so that it fills the entire page (without exceeding the page borders).

\documentclass{article}

\usepackage{parskip}

\usepackage[showframe]{geometry}

\usepackage{tikz}

\newsavebox \mybox \sbox \mybox {% \tikz{% \draw (0,0) circle (0.2); \draw (0,0) circle (0.4); \draw (0,0) circle (0.6); \draw (0,0) circle (0.8); \draw (0,0) circle (1); }% }

\begin{document} \begin{tikzpicture} \foreach \row in {2,4,...,18} { \foreach \column in {2,4,...,14} { \node at (\column, \row) {\usebox\mybox}; } } \end{tikzpicture} \end{document}

enter image description here

Sometimes the page dimension is changed using the geometry package. When that hapens, I need to manually update the number of iterations of the loop and see if the number of repetitions don't exceed the page border. I wish the loop automatically stopped when it reaches the right limit of the page or the bottom limit of the page.

I also know how to repeat something until the right limit of the page is reached by using \xleaders. (see this answer). I used that knowledge to repeat the tikz graphic (see minimal working example below). However, the problem is that multiple tikzpicture environment are used. I wished a single tikzpicture is used in order to reduce the complexity of the source code.

\documentclass{article}

\usepackage{parskip}

\usepackage[showframe]{geometry}

\usepackage{tikz}

\newsavebox \mybox \sbox \mybox {% \tikz{% \draw (0,0) circle (0.2); \draw (0,0) circle (0.4); \draw (0,0) circle (0.6); \draw (0,0) circle (0.8); \draw (0,0) circle (1); }% }

\newcommand\asteriskfill{\leavevmode\xleaders\hbox{% \begin{tikzpicture} \node at (0, 0) {\usebox\mybox}; \end{tikzpicture}% }% \hfill\kern0pt}

\begin{document} \asteriskfill \end{document}

enter image description here

I don't know how to repeat a pattern until the bottom limit of the page is reached.

The question

How to repeat the exact same TikZ pattern to use the entire available space in the page without exceeding page limits?

I thought that a possible way to tackle this problem would be to define a pattern using \newsavebox and somehow define a macro that repeats the same box horizontally and vertically until the page limits are reached.

rdrg109
  • 565
  • 3
    Is the pattern size know? Then it is just a matter of dividing the page width(and height) by that size and round down to an integer to know the loop iterations. – hpekristiansen Mar 14 '24 at 15:59
  • 1
    @rdrg109 maybe using \resizebox you can keep the pattern consistent but scale it as needed. – jessexknight Mar 14 '24 at 16:36
  • @hpekristiansen In the 3 Inkscape images shown above (i.e. when drawing circles, rectangles and sine waves), the pattern size can be inferred from the layout of the graphic. For example, the pattern size of a group of circles is twice the ratio of the biggest circle. I guess, inferring the pattern size gets more difficult as the graphic becomes more complex. For my use case, it would be convenient if I could define a pattern in a \newsavebox and find a way to repeat that pattern horizontally and vertically as much as possible. This way, I don't need to infer the pattern size. – rdrg109 Mar 14 '24 at 16:50
  • @rdrg109 Please see the code I posted as comments under the accepted answer, to avoid any calculations. Just use vertical and horizontal leaders. – yannisl Mar 15 '24 at 02:33

2 Answers2

4
\documentclass{article}
\usepackage{parskip}
\usepackage[showframe]{geometry}
\usepackage{tikz}

\newsavebox{\mybox}
\newlength{\myboxwidth}
\newlength{\myboxheight}

\NewDocumentCommand{\fillPageWithContent}{m}{%
  \newpage
  \sbox\mybox{#1}%
  \setlength{\myboxwidth}{\wd\mybox}%
  \setlength{\myboxheight}{\dimexpr\ht\mybox+\dp\mybox}%
  \noindent
  \begin{tikzpicture}[x=\myboxwidth, y=\myboxheight]
    % "parse=true" \pgfmathparse's the upper bound in the loop
    \foreach[parse=true] \row in {1, ..., \textwidth/\myboxwidth} {%
      \foreach[parse=true] \column in {1, ..., \textheight/\myboxheight} {%
        \node at (\row, \column) {\usebox\mybox};
      }
    }
  \end{tikzpicture}
  \newpage
}

\begin{document}
\fillPageWithContent{%
  \tikz{%
    \draw (0,0) circle (0.2)
          (0,0) circle (0.4)
          (0,0) circle (0.6)
          (0,0) circle (0.8)
          (0,0) circle (1);
    % enlarge bounding box to make some separation
    \path (-1.05, -1.05) (1.05, 1.05);
  }%
}

\fillPageWithContent{%
  \tikz{
    \draw (0, 0) rectangle (2,3)
          (.2, .3) rectangle (1.8, 2.7)
          (.4, .6) rectangle (1.6, 2.4)
          (.6, .8) rectangle (1.4, 2.1);
    \path (-.1, -.1) (2.1, 3.1);
  }%
}

\fillPageWithContent{%
  \tikz{
    \draw (0, 0) rectangle (1,1);
    \draw[rotate=30] (0, 0) rectangle (1,1);
    \draw[rotate=60] (0, 0) rectangle (1,1);
    \draw[rotate=90] (0, 0) rectangle (1,1);
    \draw[rotate=120] (0, 0) rectangle (1,1);    
  }
}
\end{document}

enter image description here

To make the pattern centered within text area, both horizontally and vertically, I

  • add inner sep=0pt to tikzpicture in \fillPageWithContent and
  • use \path <coordinate>; to enlarge the tikzpicture from north and west, by the needed amount of shifting. This is tricker than classic \centering and \vfill way, but it works for any size of target rectangle to fill.
\documentclass{article}
\usepackage{parskip}
\usepackage[showframe]{geometry}
\usepackage{tikz}

\usetikzlibrary{calc}

\newsavebox{\mybox} \newlength{\myboxwidth} \newlength{\myboxheight}

\NewDocumentCommand{\fillPageWithContent}{m}{% \newpage \sbox\mybox{#1}% \setlength{\myboxwidth}{\wd\mybox}% \setlength{\myboxheight}{\dimexpr\ht\mybox+\dp\mybox}% \noindent \begin{tikzpicture}[x=\myboxwidth, y=\myboxheight, inner sep=0pt] % "parse=true" \pgfmathparse's the upper bound in the loop \foreach[parse=true] \row in {1, ..., \textwidth/\myboxwidth} {% \foreach[parse=true] \column in {1, ..., \textheight/\myboxheight} {% \node at (\row, \column) {\usebox\mybox}; } } \path let \n{xshift} = {\textwidth-\myboxwidthint(\textwidth/\myboxwidth)}, \n{yshift} = {\textheight-\myboxheightint(\textheight/\myboxheight))} % Only north and west sides of current bounding box are enlarged, % since if all four sides are enlarged, the resulting tikzpicture % might be slightly larger than the target rectangle due to rounding % errors. May fail in RTL scripts. % Also the "pt" following ".5" is important: it makes the x- and % y-coordinate with units, so the unit-vectors (which we altered with % "x=\myboxwidth, y=\myboxheight") won't be applied. in ([shift={(-.5pt\n{xshift}, .5pt\n{yshift})}] current bounding box.north west); \end{tikzpicture} \newpage }

\begin{document} \fillPageWithContent{% \tikz{% \draw (0,0) circle (0.2) (0,0) circle (0.4) (0,0) circle (0.6) (0,0) circle (0.8) (0,0) circle (1); % enlarge bounding box to make some separation \path (-1.05, -1.05) (1.05, 1.05); }% }

\fillPageWithContent{% \tikz{ \draw (0, 0) rectangle (2,3) (.2, .3) rectangle (1.8, 2.7) (.4, .6) rectangle (1.6, 2.4) (.6, .8) rectangle (1.4, 2.1); \path (-.1, -.1) (2.1, 3.1); }% }

\fillPageWithContent{% \tikz{ \draw (0, 0) rectangle (1,1); \draw[rotate=30] (0, 0) rectangle (1,1); \draw[rotate=60] (0, 0) rectangle (1,1); \draw[rotate=90] (0, 0) rectangle (1,1); \draw[rotate=120] (0, 0) rectangle (1,1); } } \end{document}

enter image description here

muzimuzhi Z
  • 26,474
  • Could you with this method draw the exact same thing, but centered on the page or within textarea - I do not like it when there is just not enough room for one extra like in the middle picture? (I am not the one that needs it, but I am struggling to center everything with an other method) – hpekristiansen Mar 14 '24 at 17:14
  • Now looking at your code, I see that you would have the same problem as me. tikzpagenodes does not help much as the the picture is not symmetric around (0,0). -I guess it could be made so, but I do not see the "smart" way of doing it - assuming it exist. – hpekristiansen Mar 14 '24 at 17:24
  • @hpekristiansen See update. – muzimuzhi Z Mar 14 '24 at 18:36
  • @muzimuzhiZ Knuth had exactly the same problem. Assume each image was the letter "A". You just loop x-times and TeX will build it for you as a paragraph! – yannisl Mar 15 '24 at 01:40
  • \documentclass{article} \begin{document} \newsavebox\mybox \setbox\mybox\vbox to \textheight{% \null \xleaders\hbox{A} \vfill } \usebox\mybox \null\xleaders\hbox{\usebox\mybox}\hfill\null \end{document} – yannisl Mar 15 '24 at 02:28
  • 1
    See the code above, only using leaders. No calculations. Just replace the 'A' with the tikz code:) It will be nice if you can edit your post and add it as an alternative solution. By the way your tikz skills are impressive. – yannisl Mar 15 '24 at 02:30
2

enter image description here

Knuth designed TeX, perfectly for this. You can avoid all calculations by using both horizontal and vertical leaders.

\documentclass{article}
\usepackage{fontspec}
\usepackage{tikz}
\setmainfont{symbola}
\def\phone{☏}
\def\bullseye{\tikz\draw (0,0) circle (0.2)
          (0,0) circle (0.4)
          (0,0) circle (0.6)
          (0,0) circle (0.8)
          (0,0) circle (1);}

\begin{document} \newbox\mybox \setbox\mybox\vbox to .3\textheight{% \null \xleaders\hbox{\strut\Huge\kern.2em\bullseye\kern.2em} \vfill } \usebox\mybox \null\xleaders\hbox{\usebox\mybox}\hfill\null \end{document}

As you can see from the code, by replacing the \bullseye with the \phone, it will also react to sizing commands such as \Huge or \large. I would recommend to change all coordinates to relative units. Adding also an \fbox to the leader, you get the image I have shown.

\def\bullseye{\tikz\draw (0,0) circle (0.2)
          (0,0) circle (0.4em)
          (0,0) circle (0.6em)
          (0,0) circle (0.8em)
          (0,0) circle (1em);}
yannisl
  • 117,160
  • Seems horizontal centering alignment (which @hpekristiansen raised in comments to question and my answer) is still a problem. Maybe that's because I'm not fluent with leader primitives. – muzimuzhi Z Mar 15 '24 at 08:16
  • Btw, is the symbola font used in your example the one provided in https://github.com/zhm/symbola? – muzimuzhi Z Mar 15 '24 at 08:19
  • The symbola font was from Douros https://localfonts.eu/freefonts/greek-free-fonts/unicode-fonts-for-ancient-scripts/symbola/ It might have been copied by zhm not too sure. I had it for a very long time. Is a nice font. A perfectly filled line when centered will still be 1 line long if centered! – yannisl Mar 15 '24 at 08:31
  • 1
    Ah I saw the culprit: I reindented \bullseye by adding a newline right after ; in \def\bullseye{\tikz \draw ...;}, which introduced a stray space. Thank you for this \xleaders answer. – muzimuzhi Z Mar 15 '24 at 09:36