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.

4
  • 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. Commented Mar 14 at 15:59
  • 1
    @rdrg109 maybe using \resizebox you can keep the pattern consistent but scale it as needed. Commented Mar 14 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. Commented Mar 14 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
    Commented Mar 15 at 2:33

2 Answers 2

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-\myboxwidth*int(\textwidth/\myboxwidth)},
          \n{yshift} = {\textheight-\myboxheight*int(\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

6
  • 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) Commented Mar 14 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. Commented Mar 14 at 17:24
  • @hpekristiansen See update. Commented Mar 14 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
    Commented Mar 15 at 1:40
  • 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
    Commented Mar 15 at 2: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);}
4
  • 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. Commented Mar 15 at 8:16
  • Btw, is the symbola font used in your example the one provided in github.com/zhm/symbola? Commented Mar 15 at 8:19
  • The symbola font was from Douros localfonts.eu/freefonts/greek-free-fonts/… 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
    Commented Mar 15 at 8: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. Commented Mar 15 at 9:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .