% \iffalse meta-comment %<*internal> \iffalse % %<*readme> ---------------------------------------------------------------- celtic --- TikZ library for drawing Celtic knots E-mail: loopspace@mathforge.org Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt ---------------------------------------------------------------- This package is for the generation of Celtic knots starting from a grid of walls. % %<*internal> \fi \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX\else \expandafter\begingroup \fi % %<*install> \input docstrip.tex \keepsilent \askforoverwritefalse \preamble ---------------------------------------------------------------- celtic --- TikZ library for producing Celtic knots. E-mail: loopspace@mathforge.org Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt ---------------------------------------------------------------- \endpreamble \postamble Copyright (C) 2014 by Andrew Stacey This work may be distributed and/or modified under the conditions of the LaTeX Project Public License (LPPL), either version 1.3c of this license or (at your option) any later version. The latest version of this license is in the file: http://www.latex-project.org/lppl.txt This work is "maintained" (as per LPPL maintenance status) by Andrew Stacey. This work consists of the files celtic.dtx celtic_doc.tex and the derived files celtic.ins celtic_code.pdf tikzlibraryceltic.code.tex celtic.pdf README \endpostamble \usedir{tex/latex/celtic} \generate{ \file{tikzlibraryceltic.code.tex}{\from{\jobname.dtx}{library}} } % %\endbatchfile %<*internal> \usedir{source/latex/celtic} \generate{ \file{\jobname.ins}{\from{\jobname.dtx}{install}} } \nopreamble\nopostamble \usedir{doc/latex/celtic} \generate{ \file{README.txt}{\from{\jobname.dtx}{readme}} } \ifx\fmtname\nameofplainTeX \expandafter\endbatchfile \else \expandafter\endgroup \fi % %<*driver> \documentclass[full]{l3doc} \usepackage[T1]{fontenc} \usepackage{lmodern} \usepackage{tikz} \usepackage{trace} \usetikzlibrary{celtic} %\traceoff %\usepackage[numbered]{hypdoc} \definecolor{lstbgcolor}{rgb}{0.9,0.9,0.9} \usepackage{listings} \lstloadlanguages{[LaTeX]TeX} \lstset{breakatwhitespace=true,breaklines=true,language=TeX} \usepackage{fancyvrb} \newenvironment{example} {\VerbatimEnvironment \begin{VerbatimOut}[gobble=2]{example.out}} {\end{VerbatimOut} \begin{center} % \setlength{\parindent}{0pt} \fbox{\begin{minipage}{.9\linewidth} \lstset{breakatwhitespace=true,breaklines=true,language=TeX,basicstyle=\small} \lstinputlisting[]{example.out} \end{minipage}} \fbox{\begin{minipage}{.9\linewidth} \input{example.out} \end{minipage}} \end{center} } \EnableCrossrefs \CodelineIndex \RecordChanges \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \CheckSum{783} % % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z % Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z % Digits \0\1\2\3\4\5\6\7\8\9 % Exclamation \! Double quote \" Hash (number) \# % Dollar \$ Percent \% Ampersand \& % Acute accent \' Left paren \( Right paren \) % Asterisk \* Plus \+ Comma \, % Minus \- Point \. Solidus \/ % Colon \: Semicolon \; Less than \< % Equals \= Greater than \> Question mark \? % Commercial at \@ Left bracket \[ Backslash \\ % Right bracket \] Circumflex \^ Underscore \_ % Grave accent \` Left brace \{ Vertical bar \| % Right brace \} Tilde \~} % % % \changes{1.0}{2014/05/23}{Converted to DTX file} % % \DoNotIndex{\newcommand,\newenvironment} % % \providecommand*{\url}{\texttt} % \title{The \textsf{celtic} package} % \author{Andrew Stacey \\ \url{loopspace@mathforge.org}} % \date{1.1 from 2016/02/19} % % % \maketitle % % % \section{Introduction} % % This is a TikZ library for drawing Celtic knot diagrams. % For user documentation, see the \Verb+celtic.pdf+ file. % % \StopEventually{} % % \section{Implementation} % % \iffalse %<*library> % \fi % \subsection{Initialisation} % % Load the \LaTeX3 basics ... % \begin{macrocode} \usepackage{expl3} \usepackage{xparse} % \end{macrocode} % ... and enter the Realm of the 3rd \LaTeX. % \begin{macrocode} \ExplSyntaxOn % \end{macrocode} % Wrapper around \Verb+\tikz@scan@one@point+ for the \Verb+add=+ key. % \begin{macrocode} \cs_new_nopar:Npn \celtic_shift:n #1 { \use:c{tikz@scan@one@point}\pgftransformshift #1\relax } % \end{macrocode} % % We need one or two variables ... % \begin{macrocode} \int_new:N \l__celtic_max_steps_int \int_new:N \l__celtic_int \int_new:N \l__celtic_flip_int \int_new:N \l__celtic_width_int \int_new:N \l__celtic_height_int \int_new:N \l__celtic_x \int_new:N \l__celtic_y \int_new:N \l__celtic_dx \int_new:N \l__celtic_dy \int_new:N \l__celtic_ox \int_new:N \l__celtic_oy \int_new:N \l__celtic_lout \int_new:N \l__celtic_cross_int \int_new:N \l__celtic_component_int \fp_new:N \l__celtic_clip_fp \fp_new:N \l__celtic_inner_clip_fp \fp_new:N \l__celtic_inner_fp \fp_new:N \l__celtic_outer_fp \seq_new:N \l__celtic_path_seq \seq_new:N \l__celtic_overpath_seq \seq_new:N \l__celtic_component_seq \seq_new:N \l__celtic_crossing_seq \seq_new:N \l__celtic_tmpa_seq \clist_new:N \l__celtic_tmpa_clist \tl_new:N \l__celtic_tmpa_tl \tl_new:N \l__celtic_path_tl %\tl_new:N \c__celtic_colon_tl \tl_new:N \l__celtic_bar_tl \tl_new:N \l__celtic_active_bar_tl \tl_new:N \l__celtic_start_tl \bool_new:N \l__celtic_bounce_bool \bool_new:N \l__celtic_pbounce_bool \cs_new_nopar:Npn \tl_split_after:Nnn #1#2#3 { \cs_set:Npn \tl_split_aux:nnn ##1#3##2 \q_stop: {#3##2} \tl_set:Nx #1 {\tl_split_aux:nnn #2 \q_stop:} } \cs_generate_variant:Nn \tl_split_after:Nnn {NVn} \cs_new_nopar:Npn \tl_split_before:Nnn #1#2#3 { \cs_set:Npn \tl_split_aux:nnn ##1#3##2 \q_stop: {##1#3} \tl_set:Nx #1 {\tl_split_aux:nnn #2 \q_stop:} } \cs_generate_variant:Nn \tl_split_before:Nnn {NVn} % \end{macrocode} % Define our warning message. % \begin{macrocode} \msg_new:nnnn { celtic } { max~ steps } { Limit~ of~ number~ of~ steps~ exceeded~ \msg_line_context:.} { Paths~ may~ not~ be~ correctly~ constructed.~ Consider~ raising~ the~ limit~ from \int_use:N \l__celtic_max_steps_int.} % \end{macrocode} % Using a colon for a range separator was possibly not the best idea I ever had, seeing as \LaTeX3 alters its catcode. % So we need to get creative. % \begin{macrocode} \tl_const:Nx \c__celtic_colon_tl { \token_to_str:N : } % \end{macrocode} % Some packages mess with the catcode of \Verb+|+. % \begin{macrocode} \tl_set:Nn \l__celtic_bar_tl {|} \group_begin: \char_set_catcode_active:N \| \tl_gset:Nn \l__celtic_active_bar_tl {|} \group_end: % \end{macrocode} % We need a few variants of standard \LaTeX3 functions. % \begin{macrocode} \cs_generate_variant:Nn \tl_if_single_p:N {c} \cs_generate_variant:Nn \tl_if_single:NTF {cTF} \cs_generate_variant:Nn \tl_if_eq:nnTF {xnTF} \cs_generate_variant:Nn \tl_head:N {c} \cs_generate_variant:Nn \tl_tail:N {c} \cs_generate_variant:Nn \tl_if_eq:nnTF {vnTF} \cs_generate_variant:Nn \tl_if_in:nnTF {nVTF} % \end{macrocode} % Initialise a few variables. % \begin{macrocode} \int_set:Nn \l__celtic_max_steps_int {20} \fp_set:Nn \l__celtic_inner_fp {1} \fp_set:Nn \l__celtic_outer_fp {2} % \end{macrocode} % % The following functions are for parsing and setting the crossing information. % \begin{macro}{\celtic_do_crossing:nnn} % This function sets the information for a particular crossing. % The first argument can be empty, meaning ``ignore this crossing as a starting point'', or it should be one of \Verb+|+ or \Verb+-+ to denote the wall type that is placed at this crossing. % \begin{macrocode} \cs_new_nopar:Npn \celtic_do_crossing:nnn #1#2#3 { \tl_if_empty:nTF {#1} { \tl_clear:c {crossing used \int_eval:n {#2} - \int_eval:n {#3}} } { \tl_set:cn {crossing \int_eval:n {#2} - \int_eval:n{#3}}{#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_maybe_symmetric:nnnn} % If a crossing is designated as symmetric, we repeat the action four times. % This macro tests to see if it is symmetric or not and acts accordingly. % \begin{macrocode} \cs_new_nopar:Npn \celtic_maybe_symmetric:nnnn #1#2#3#4 { \tl_if_empty:nTF {#1} { \celtic_do_crossing:nnn {#2}{#3}{#4} } { \celtic_do_crossing:nnn {#2}{#3}{#4} \celtic_do_crossing:nnn {#2}{\l__celtic_width_int - #3}{#4} \celtic_do_crossing:nnn {#2}{#3}{\l__celtic_height_int - #4} \celtic_do_crossing:nnn {#2}{\l__celtic_width_int - #3}{\l__celtic_height_int - #4} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_maybe_xrange:nnnn} % The \Verb+x+-coordinate might be a range. % If it is, it contains a colon (with the normal catcode). % So we test for a colon and act accordingly. % \begin{macrocode} \cs_new_nopar:Npn \celtic_maybe_xrange:nnnn #1#2#3#4 { \tl_if_in:nVTF {#3} \c__celtic_colon_tl { \celtic_do_xrange:w {#1}{#2}#3\q_stop{#4} } { \celtic_maybe_yrange:nnnn {#1}{#2}{#3}{#4} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_maybe_yrange:nnnn} % Same with the \Verb+y+-coordinate. % \begin{macrocode} \cs_new_nopar:Npn \celtic_maybe_yrange:nnnn #1#2#3#4 { \tl_if_in:nVTF {#4} \c__celtic_colon_tl { \celtic_do_yrange:w {#1}{#2}{#3}#4\q_stop } { \celtic_maybe_symmetric:nnnn {#1}{#2}{#3}{#4} } } % \end{macrocode} % \end{macro} % % When processing ranges, we need to use colons with the original catcode. % We've stored one in \Verb+\c__celtic_colon_tl+ but we need to use it in actuality. % So we make a token list containing the definitions we want to make, expanding \Verb+\c__celtic_colon_tl+ to its colon, but not expanding anything else. % \begin{macrocode} \tl_set:Nx \l_tmpa_tl { % \end{macrocode} % % \begin{macro}{\celtic_do_xrange:w} % This splits the \Verb+x+-coordinate into a range and repeats the function for each intermediate value. % \begin{macrocode} \exp_not:N \cs_new_nopar:Npn \exp_not:N \celtic_do_xrange:w ##1##2##3\tl_use:N \c__celtic_colon_tl ##4\exp_not:N \q_stop##5 { \exp_not:N \int_step_inline:nnnn {##3} {2} {##4} { \exp_not:N \celtic_maybe_yrange:nnnn {##1}{##2} {####1}{##5} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_do_yrange:w} % Same, for the \Verb+y+-coordinate. % \begin{macrocode} \exp_not:N \cs_new_nopar:Npn \exp_not:N \celtic_do_yrange:w ##1##2##3##4\tl_use:N \c__celtic_colon_tl ##5\exp_not:N \q_stop { \exp_not:N \int_step_inline:nnnn {##4} {2} {##5} { \exp_not:N \celtic_maybe_symmetric:nnnn {##1}{##2}{##3}{####1} } } } % \end{macrocode} % \end{macro} % % Now we use the above token list to make our definitions with the right colon in them. % \begin{macrocode} \tl_use:N \l_tmpa_tl % \end{macrocode} % % The next functions are those that take the individual crossing specifications from the key/value list and begin the process of converting the data to an action to be taken for a specific crossing. % \begin{macro}{\celtic_ignore_crossings:w} % \begin{macrocode} \cs_new_nopar:Npn \celtic_ignore_crossings:w #1,#2\q_stop { \celtic_maybe_xrange:nnnn {}{}{#1}{#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_ignore_symmetric_crossings:w} % \begin{macrocode} \cs_new_nopar:Npn \celtic_ignore_symmetric_crossings:w #1,#2\q_stop { \celtic_maybe_xrange:nnnn {s}{}{#1}{#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_set_crossings:w} % \begin{macrocode} \cs_new_nopar:Npn \celtic_set_crossings:w #1,#2,#3\q_stop { \celtic_maybe_xrange:nnnn {}{#3}{#1}{#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_set_symmetric_crossings:w} % \begin{macrocode} \cs_new_nopar:Npn \celtic_set_symmetric_crossings:w #1,#2,#3\q_stop { \celtic_maybe_xrange:nnnn {s}{#3}{#1}{#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_next_crossing:} % This is the function that does all the work. % Starting from an undercrossing, it computes the segment leading to the next undercrossing working out all of the ``bounces'' on the way. % \begin{macrocode} \cs_new_nopar:Npn \celtic_next_crossing: { % \end{macrocode} % Clear our starting conditions. % \begin{macrocode} \int_zero:N \l__celtic_cross_int \tl_clear:N \l__celtic_crossing_tl \tl_clear:N \l__celtic_path_tl \tl_clear:N \l__celtic_overpath_tl \bool_set_false:N \l__celtic_bounce_tl \tl_set:Nx \l__celtic_start_tl {(\int_use:N \l__celtic_x, \int_use:N \l__celtic_y)} % \end{macrocode} % Start our path with a move to the initial point and record our current direction. % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl {(\int_use:N \l__celtic_x, \int_use:N \l__celtic_y)} \int_set:Nn \l__celtic_lout {(90 - \l__celtic_dx * 45) * \l__celtic_dy} % \end{macrocode} % We loop until we get to the second crossing on the path (the first will be the overpass). % \begin{macrocode} \bool_do_until:nn {\int_compare_p:n {\l__celtic_cross_int > 1}} { % \end{macrocode} % We keep a record of whether the last bit contained a bounce. % \begin{macrocode} \bool_set_eq:NN \l__celtic_pbounce_bool \l__celtic_bounce_bool \bool_set_false:N \l__celtic_bounce_bool % \end{macrocode} % Move to the next point in our current direction. % \begin{macrocode} \int_add:Nn \l__celtic_x {\l__celtic_dx} \int_add:Nn \l__celtic_y {\l__celtic_dy} % \end{macrocode} % Now we look to see if we should bounce. % Is the crossing defined? % \begin{macrocode} \tl_if_exist:cT {crossing \int_use:N \l__celtic_x - \int_use:N \l__celtic_y} { % \end{macrocode} % Yes, so we bounce. % But which way? % \begin{macrocode} \tl_if_eq:cNTF {crossing \int_use:N \l__celtic_x - \int_use:N \l__celtic_y} \l__celtic_bar_tl { % \end{macrocode} % Vertical wall. % Have we just bounced? % \begin{macrocode} \bool_if:NTF \l__celtic_pbounce_bool { % \end{macrocode} % Yes, so the next part of the path is a right angle. % \begin{macrocode} \tl_put_right:Nn \l__celtic_path_tl { -| } } { % \end{macrocode} % No, so the next part of the path is a curve. % (This is where we use the direction that we recorded earlier.) % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_eval:n {(90 - 45*\l__celtic_dx)*\l__celtic_dy}, in=\int_eval:n {-90*\l__celtic_dy}] } } % \end{macrocode} % We record the new direction and ``bounce'' our direction vector. % Then we add our new point to the path (which, due to the bounce, is offset). % \begin{macrocode} \int_set:Nn \l__celtic_lout {90*\l__celtic_dy} \int_set:Nn \l__celtic_dx {-\l__celtic_dx} \tl_put_right:Nx \l__celtic_path_tl {(\fp_eval:n {\int_use:N \l__celtic_x + .5 * \int_use:N \l__celtic_dx}, \int_use:N \l__celtic_y)} % \end{macrocode} % We bounced, so record that too. % \begin{macrocode} \bool_set_true:N \l__celtic_bounce_bool } { % \end{macrocode} % At this point, we've bounced but our bounce was horizontal so we do the same as for the vertical but all turned round. % \begin{macrocode} \bool_if:NTF \l__celtic_pbounce_bool { % \end{macrocode} % We're out from a bounce, so turn at right angles. % \begin{macrocode} \tl_put_right:Nn \l__celtic_path_tl { |- } } { % \end{macrocode} % We're not out from a bounce, so we curve ... % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_eval:n {(90 - 45*\l__celtic_dx)*\l__celtic_dy}, in=\int_eval:n {90 + 90*\l__celtic_dx}] } } % \end{macrocode} % ... and record our new direction and out angle. % \begin{macrocode} \int_set:Nn \l__celtic_lout {90-90*\l__celtic_dx} \int_set:Nn \l__celtic_dy {-\l__celtic_dy} % \end{macrocode} % Now we add our new position (adjusted from the bounce) to the path. % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl {(\int_use:N \l__celtic_x, \fp_eval:n {\int_use:N \l__celtic_y + .5 * \int_use:N \l__celtic_dy})} % \end{macrocode} % And record the fact that we've bounced. % \begin{macrocode} \bool_set_true:N \l__celtic_bounce_bool } } % \end{macrocode} % Now we check to see if we're at the edge of the rectangle, starting with the left. % \begin{macrocode} \int_compare:nT {\l__celtic_x == 0} { % \end{macrocode} % Yes, so treat this as a vertical bounce. % \begin{macrocode} \bool_if:NTF \l__celtic_pbounce_bool { % \end{macrocode} % Previous bounce, so right angle. % \begin{macrocode} \tl_put_right:Nn \l__celtic_path_tl { -| } } { % \end{macrocode} % No previous bounce, so curve. % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_eval:n {(90 - 45*\l__celtic_dx)*\l__celtic_dy}, in=\int_eval:n {-90*\l__celtic_dy}] } } % \end{macrocode} % Record our out angle and change our direction. % \begin{macrocode} \int_set:Nn \l__celtic_lout {90*\l__celtic_dy} \int_set:Nn \l__celtic_dx {-\l__celtic_dx} % \end{macrocode} % Add the correct position to the path. % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl {(\fp_eval:n {\int_use:N \l__celtic_x + .5 * \int_use:N \l__celtic_dx}, \int_use:N \l__celtic_y)} % \end{macrocode} % We've bounced. % \begin{macrocode} \bool_set_true:N \l__celtic_bounce_bool } % \end{macrocode} % Same for the right-hand edge. % \begin{macrocode} \int_compare:nT {\l__celtic_x == \l__celtic_width_int} { \bool_if:NTF \l__celtic_pbounce_bool { \tl_put_right:Nn \l__celtic_path_tl { -| } } { \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_eval:n {(90 - 45*\l__celtic_dx)*\l__celtic_dy}, in=\int_eval:n {-90*\l__celtic_dy}] } } \int_set:Nn \l__celtic_lout {90*\l__celtic_dy} \int_set:Nn \l__celtic_dx {-\l__celtic_dx} \tl_put_right:Nx \l__celtic_path_tl {(\fp_eval:n {\int_use:N \l__celtic_x + .5 * \int_use:N \l__celtic_dx}, \int_use:N \l__celtic_y)} \bool_set_true:N \l__celtic_bounce_bool } % \end{macrocode} % Now the lower edge. % \begin{macrocode} \int_compare:nT {\l__celtic_y == 0} { \bool_if:NTF \l__celtic_pbounce_bool { \tl_put_right:Nn \l__celtic_path_tl { |- } } { \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_eval:n {(90 - 45*\l__celtic_dx)*\l__celtic_dy}, in=\int_eval:n {90 + 90*\l__celtic_dx}] } } \int_set:Nn \l__celtic_lout {90-90*\l__celtic_dx} \int_set:Nn \l__celtic_dy {-\l__celtic_dy} \tl_put_right:Nx \l__celtic_path_tl {(\int_use:N \l__celtic_x, \fp_eval:n {\int_use:N \l__celtic_y + .5 * \int_use:N \l__celtic_dy})} \bool_set_true:N \l__celtic_bounce_bool } % \end{macrocode} % And the upper edge. % \begin{macrocode} \int_compare:nT {\l__celtic_y == \l__celtic_height_int} { \bool_if:NTF \l__celtic_pbounce_bool { \tl_put_right:Nn \l__celtic_path_tl { |- } } { \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_eval:n {(90 - 45*\l__celtic_dx)*\l__celtic_dy}, in=\int_eval:n {90 + 90*\l__celtic_dx}] } } \int_set:Nn \l__celtic_lout {-90+90*\l__celtic_dx} \int_set:Nn \l__celtic_dy {-\l__celtic_dy} \tl_put_right:Nx \l__celtic_path_tl {(\int_use:N \l__celtic_x, \fp_eval:n {\int_use:N \l__celtic_y + .5 * \int_use:N \l__celtic_dy})} \bool_set_true:N \l__celtic_bounce_bool } % \end{macrocode} % Did we bounce this time? % \begin{macrocode} \bool_if:NF \l__celtic_bounce_bool { % \end{macrocode} % Did we bounce last time? % \begin{macrocode} \bool_if:NTF \l__celtic_pbounce_bool { % \end{macrocode} % Yes, so the second half is a curve. % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl { to[out=\int_use:N \l__celtic_lout,in=\int_eval:n {(-90 - 45 * \l__celtic_dx)*\l__celtic_dy}] } } { % \end{macrocode} % No, so the second half is a straight line. % \begin{macrocode} \tl_put_right:Nn \l__celtic_path_tl { -- } } % \end{macrocode} % The next crossing. % \begin{macrocode} \tl_put_right:Nx \l__celtic_path_tl { (\int_use:N \l__celtic_x, \int_use:N \l__celtic_y)} % \end{macrocode} % If we haven't already gone over a crossing, this is our overcrossing. % \begin{macrocode} \tl_if_empty:NTF \l__celtic_crossing_tl { % \end{macrocode} % So we record this as our overcrossing. % \begin{macrocode} \tl_set:Nx \l__celtic_crossing_tl {(\int_use:N \l__celtic_x, \int_use:N \l__celtic_y)} } { % \end{macrocode} % Otherwise, it's the undercrossing so we note that we've visited this one. % \begin{macrocode} \tl_clear:c {crossing used \int_use:N \l__celtic_x - \int_use:N \l__celtic_y} } % \end{macrocode} % Increment the crossing count. % \begin{macrocode} \int_incr:N \l__celtic_cross_int % \end{macrocode} % Record our outward angle. % \begin{macrocode} \int_set:Nn \l__celtic_lout {(90 - \l__celtic_dx * 45) * \l__celtic_dy} } } % \end{macrocode} % Is our overcrossing one of the undercrossings? % If so, remove the initial or final segment as appropriate. % \begin{macrocode} \tl_set_eq:NN \l__celtic_overpath_tl \l__celtic_path_tl \tl_set:Nx \l__celtic_tmpa_tl {(\int_use:N \l__celtic_x, \int_use:N \l__celtic_y)} \tl_if_eq:NNT \l__celtic_crossing_tl \l__celtic_tmpa_tl { \tl_reverse:N \l__celtic_overpath_tl \tl_set:Nx \l__celtic_overpath_tl {\tl_tail:N \l__celtic_overpath_tl} \tl_split_after:NVn \l__celtic_overpath_tl \l__celtic_overpath_tl {)} \tl_reverse:N \l__celtic_overpath_tl } \tl_if_eq:NNT \l__celtic_crossing_tl \l__celtic_start_tl { \tl_set:Nx \l__celtic_overpath_tl {\tl_tail:N \l__celtic_overpath_tl} \tl_split_after:NVn \l__celtic_overpath_tl \l__celtic_overpath_tl {(} } } % \end{macrocode} % \end{macro} % % Now we set up the keys we'll use. % \begin{macrocode} \keys_define:nn { celtic } { % \end{macrocode} % This sets the maximum number of steps in a path. % \begin{macrocode} max~ steps .int_set:N = \l__celtic_max_steps_int, % \end{macrocode} % This flips the over/under crossings. % \begin{macrocode} flip .code:n = { \int_set:Nn \l__celtic_flip_int {-1} }, % \end{macrocode} % These set the size of the knot. % \begin{macrocode} width .int_set:N = \l__celtic_width_int, height .int_set:N = \l__celtic_height_int, size .code:n = { % \end{macrocode} % The size is a CSV so we use a \Verb+clist+ to separate it. % \begin{macrocode} \clist_set:Nn \l__celtic_tmpa_clist {#1} \clist_pop:NN \l__celtic_tmpa_clist \l__celtic_tmpa_tl \int_set:Nn \l__celtic_width_int {\l__celtic_tmpa_tl} \clist_pop:NN \l__celtic_tmpa_clist \l__celtic_tmpa_tl \int_set:Nn \l__celtic_height_int {\l__celtic_tmpa_tl} }, % \end{macrocode} % The size keys are placed in a separate group to make it possible to process them before all other keys. % \begin{macrocode} width .groups:n = { size }, height .groups:n = { size }, size .groups:n = { size }, % \end{macrocode} % The next keys set the various crossing behaviours. % \begin{macrocode} crossings .code:n = { \seq_set_split:Nnn \l__celtic_tmpa_seq {;} {#1} \seq_map_inline:Nn \l__celtic_tmpa_seq { \tl_if_empty:nF {##1} { \celtic_set_crossings:w ##1 \q_stop } } }, % \end{macrocode} % % \begin{macrocode} symmetric~ crossings .code:n = { \seq_set_split:Nnn \l__celtic_tmpa_seq {;} {#1} \seq_map_inline:Nn \l__celtic_tmpa_seq { \tl_if_empty:nF {##1} { \celtic_set_symmetric_crossings:w ##1 \q_stop } } }, % \end{macrocode} % % \begin{macrocode} ignore~ crossings .code:n ={ \seq_set_split:Nnn \l__celtic_tmpa_seq {;} {#1} \seq_map_inline:Nn \l__celtic_tmpa_seq { \tl_if_empty:nF {##1} { \celtic_ignore_crossings:w ##1 \q_stop } } }, % \end{macrocode} % % \begin{macrocode} ignore~ symmetric~ crossings .code:n ={ \seq_set_split:Nnn \l__celtic_tmpa_seq {;} {#1} \seq_map_inline:Nn \l__celtic_tmpa_seq { \tl_if_empty:nF {##1} { \celtic_ignore_symmetric_crossings:w ##1 \q_stop } } }, % \end{macrocode} % The \Verb+style+ key is passed on to \Verb+\tikzset+. % \begin{macrocode} style .code:n = { \tikzset {#1} }, % \end{macrocode} % This relocates the diagram. % \begin{macrocode} at .code:n = { \celtic_shift:n {#1} }, % \end{macrocode} % These set the margin for the clip regions. % \begin{macrocode} inner~ clip .fp_set:N = \l__celtic_inner_fp, outer~ clip .fp_set:N = \l__celtic_outer_fp, } % \end{macrocode} % % \begin{macro}{\CelticDrawPath} % This is the user macro. % Its mandatory argument is a list of key/value pairs. % \begin{macrocode} \DeclareDocumentCommand \CelticDrawPath { m } { % \end{macrocode} % Get a nice clean initial state. % \begin{macrocode} \group_begin: \pgfscope \seq_clear:N \l__celtic_path_seq \seq_clear:N \l__celtic_overpath_seq \seq_clear:N \l__celtic_component_seq \seq_clear:N \l__celtic_crossing_seq \int_set:Nn \l__celtic_flip_int {1} % \end{macrocode} % Figure out if \Verb+|+ is active or not (\Verb+fancyvrb+ sets it active). % \begin{macrocode} \int_compare:nT {\char_value_catcode:n {`\|} = 13} { \tl_set_eq:NN \l__celtic_bar_tl \l__celtic_active_bar_tl } % \end{macrocode} % Clear all the crossing data. % \begin{macrocode} \int_step_inline:nnnn {1} {1} {\l__celtic_height_int-1} { \int_step_inline:nnnn {1 + \int_mod:nn {##1}{2}} {2} {\l__celtic_width_int-1} { \tl_clear_new:c {crossing used ####1 - ##1} \tl_set:cn {crossing used ####1 - ##1} {X} } } % \end{macrocode} % Process the keys relating to the size of the knot. % \begin{macrocode} \keys_set_groups:nnn { celtic } { size } {#1} % \end{macrocode} % Process all other keys. % \begin{macrocode} \keys_set_filter:nnn { celtic } { size } {#1} % \end{macrocode} % Draw (maybe) the outer boundary. % \begin{macrocode} \path[celtic~ bar/.try, celtic~ surround/.try] (0,0) rectangle (\int_use:N \l__celtic_width_int, \int_use:N \l__celtic_height_int); % \end{macrocode} % Draw (maybe) the crossings. % \begin{macrocode} \int_step_inline:nnnn {1} {1} {\l__celtic_height_int-1} { \int_step_inline:nnnn {1 + \int_mod:nn {##1}{2}} {2} {\l__celtic_width_int-1} { \tl_if_exist:cT {crossing ####1 - ##1} { \tl_if_eq:cNTF {crossing ####1 - ##1} \l__celtic_bar_tl { % \end{macrocode} % Vertical crossing. % \begin{macrocode} \path[celtic~ bar/.try] (####1,##1-1) -- (####1,##1+1); } { % \end{macrocode} % Horizontal crossing. % \begin{macrocode} \path[celtic~ bar/.try] (####1-1,##1) -- (####1+1,##1); } } } } % \end{macrocode} % Now we work through the crossings, trying to generate a path starting at each one. % The crossings are at points \((x,y)\) with \(x + y\) odd. % \begin{macrocode} \int_step_inline:nnnn {1} {1} {\l__celtic_height_int-1} { \int_step_inline:nnnn {1 + \int_mod:nn {##1}{2}} {2} {\l__celtic_width_int-1} { % \end{macrocode} % Attempt to generate a path starting from that crossing. % The third argument is to indicate which way the under-path goes from that crossing. % \begin{macrocode} \celtic_generate_path:nnx {####1}{##1}{\int_eval:n {\l__celtic_flip_int*(2*\int_mod:nn{####1}{2} - 1)}} } } % \end{macrocode} % Once we have generated our paths, we render them and close our scope and group. % \begin{macrocode} \celtic_render_path: \endpgfscope \group_end: } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_generate_path:nnn} % This macro generates a sequence of path segments. % \begin{macrocode} \cs_new_nopar:Npn \celtic_generate_path:nnn #1#2#3 { % \end{macrocode} % First off, we test to see if the given coordinates are allowed as a starting point. % If the crossing has a wall or it is already marked as ``used'' then it isn't. % \begin{macrocode} \bool_if:nF { \tl_if_exist_p:c {crossing #1 - #2} || \tl_if_empty_p:c {crossing used #1 - #2} } { % \end{macrocode} % Those tests failed, so we proceed. % First, we mark the crossing as used and set our initial data. % Position, original position, and direction. % \begin{macrocode} \tl_clear:c {crossing used #1 - #2} \int_incr:N \l__celtic_component_int \int_set:Nn \l__celtic_x {#1} \int_set:Nn \l__celtic_y {#2} \int_set_eq:NN \l__celtic_ox \l__celtic_x \int_set_eq:NN \l__celtic_oy \l__celtic_y \int_set:Nn \l__celtic_dx {#3} \int_set:Nn \l__celtic_dy {1} % \end{macrocode} % This holds our recursion index so that we can bail out if we look like we're entering a loop (which we shouldn't). % \begin{macrocode} \int_zero:N \l__celtic_int % \end{macrocode} % We stop the loop if we get back where we started or we hit the maximum recursion limit. % \begin{macrocode} \bool_do_until:nn { (\int_compare_p:n {\l__celtic_x == \l__celtic_ox} && \int_compare_p:n {\l__celtic_y == \l__celtic_oy} ) || \int_compare_p:n {\l__celtic_int > \l__celtic_max_steps_int} } { % \end{macrocode} % Increment our counter. % \begin{macrocode} \int_incr:N \l__celtic_int % \end{macrocode} % Create the segment between this crossing and the next one. % \begin{macrocode} \celtic_next_crossing: % \end{macrocode} % Store the segment, its over-crossing, and its component number. % Then return to the start of the loop. % \begin{macrocode} \seq_put_left:NV \l__celtic_path_seq \l__celtic_path_tl \seq_put_left:NV \l__celtic_overpath_seq \l__celtic_overpath_tl \seq_put_left:NV \l__celtic_crossing_seq \l__celtic_crossing_tl \seq_put_left:NV \l__celtic_component_seq \l__celtic_component_int } % \end{macrocode} % If we hit the maximum number of steps, issue a warning. % \begin{macrocode} \int_compare:nT {\l__celtic_int > \l__celtic_max_steps_int} { \msg_warning:nn {celtic} { max~ steps } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_generate_path:nnx} % Useful variant. % \begin{macrocode} \cs_generate_variant:Nn \celtic_generate_path:nnn {nnx} % \end{macrocode} % \end{macro} % % \begin{macro}{\celtic_render_path:} % This takes a generated list of path segments and renders them. % \begin{macrocode} \cs_new_nopar:Npn \celtic_render_path: { % \end{macrocode} % First pass through the sequence of segments. % \begin{macrocode} \seq_map_inline:Nn \l__celtic_path_seq { % \end{macrocode} % We need to get the component number, but \Verb+pop+ removes it from the sequence so we put it back at the other end again. % \begin{macrocode} \seq_pop:NN \l__celtic_component_seq \l__celtic_tmpa_tl \seq_put_right:NV \l__celtic_component_seq \l__celtic_tmpa_tl % \end{macrocode} % Draw the path segment, styling by the component number. % \begin{macrocode} \path[celtic~ path/.try, celtic~ path~ \tl_use:N \l__celtic_tmpa_tl/.try] ##1; } % \end{macrocode} % This next bit of code attempts to work out the true thickness of the presumably doubled path. % We do it in a group and scope to limit its effect. % \begin{macrocode} \group_begin: \pgfscope \tikzset{celtic~ path/.try} \tl_use:c {tikz@double@setup} % \end{macrocode} % This gets the resulting line width outside the group and scope. % \begin{macrocode} \tl_set:Nn \l__celtic_tmpa_tl { \endpgfscope \group_end: \fp_set:Nn \l__celtic_clip_fp } \tl_put_right:Nx \l__celtic_tmpa_tl {{\dim_use:N \pgflinewidth}} \tl_use:N \l__celtic_tmpa_tl % \end{macrocode} % Now we set the inner and outer clip sizes based on that line width. % \begin{macrocode} \fp_set:Nn \l__celtic_inner_clip_fp {sqrt(2) * (\l__celtic_clip_fp + \l__celtic_inner_fp)} \fp_set:Nn \l__celtic_clip_fp {sqrt(2) * (\l__celtic_clip_fp + \l__celtic_outer_fp)} % \end{macrocode} % % This second pass through the segments redraws each one clipped to a diamond neighbourhood of its over-crossing. % \begin{macrocode} \seq_map_inline:Nn \l__celtic_overpath_seq { % \end{macrocode} % We get the crossing coordinate. % \begin{macrocode} \seq_pop:NN \l__celtic_crossing_seq \l__celtic_crossing_tl % \end{macrocode} % Again, we need the component number. % \begin{macrocode} \seq_pop:NN \l__celtic_component_seq \l__celtic_tmpa_tl \seq_put_right:NV \l__celtic_component_seq \l__celtic_tmpa_tl \pgfscope % \end{macrocode} % This is the smaller of the clip regions. % \begin{macrocode} \clip \l__celtic_crossing_tl +(-\fp_to_dim:N \l__celtic_inner_clip_fp,0) -- +(0,\fp_to_dim:N \l__celtic_inner_clip_fp) -- +(\fp_to_dim:N \l__celtic_inner_clip_fp,0) -- +(0,-\fp_to_dim:N \l__celtic_inner_clip_fp) -- +(-\fp_to_dim:N \l__celtic_inner_clip_fp,0); % \end{macrocode} % We draw just the background part of the (presumably doubled) path. % \begin{macrocode} \path[celtic~ path/.try, celtic~ path~ \tl_use:N \l__celtic_tmpa_tl/.try, double~ background] ##1; \endpgfscope \pgfscope % \end{macrocode} % Noew we apply the larger clip region. % \begin{macrocode} \clip \l__celtic_crossing_tl +(-\fp_to_dim:N \l__celtic_clip_fp,0) -- +(0,\fp_to_dim:N \l__celtic_clip_fp) -- +(\fp_to_dim:N \l__celtic_clip_fp,0) -- +(0,-\fp_to_dim:N \l__celtic_clip_fp) -- +(-\fp_to_dim:N \l__celtic_clip_fp,0); % \end{macrocode} % And draw the foreground part. % \begin{macrocode} \path[celtic~ path/.try, celtic~ path~ \tl_use:N \l__celtic_tmpa_tl/.try,double~ foreground] ##1; \endpgfscope } } % \end{macrocode} % \end{macro} % We are now leaving \LaTeX3 world. % \begin{macrocode} \ExplSyntaxOff % \end{macrocode} % % Clipping with doubled paths isn't perfect when anti-aliasing is used as it produces artefacts where the lower path shows through. % To get round that, we need to draw the two parts of the doubled path separately. % The following two keys extract the line widths and colours of the two parts of a doubled path and apply it. % \begin{macrocode} \tikzset{ % \end{macrocode} % This sets the stye to that of the under path. % \begin{macrocode} double background/.code={% \begingroup \tikz@double@setup \global\pgf@xa=\pgflinewidth \endgroup \expandafter\tikz@semiaddlinewidth\expandafter{\the\pgf@xa}% \tikz@addmode{\tikz@mode@doublefalse}% }, % \end{macrocode} % This to the over path. % \begin{macrocode} double foreground/.code={% \begingroup \tikz@double@setup \global\pgf@xa=\pgfinnerlinewidth \endgroup \expandafter\tikz@semiaddlinewidth\expandafter{\the\pgf@xa}% \tikz@addmode{\tikz@mode@doublefalse}% \tikzset{color=\pgfinnerstrokecolor}% }, } % \end{macrocode} % % \iffalse % % \fi %\Finale \endinput