% \iffalse meta-comment %% ============================================================================= %% %% string-diagrams 0.2.1 (2023/06/13) %% %% Copyright (C) 2023 by Paolo Brasolin %% SPDX-License-Identifier: LPPL-1.3c %% %% ============================================================================= %% %% This work may be distributed and/or modified under the %% conditions of the LaTeX Project Public License, either version 1.3c %% of this license or (at your option) any later version. %% The latest version of this license is in %% https://www.latex-project.org/lppl.txt %% and version 1.3c or later is part of all distributions of LaTeX %% version 2008 or later. %% %% This work has the LPPL maintenance status `author-maintained'. %% %% The Current Maintainer of this work is Paolo Brasolin. %% %% This work consists of the files README.md, %% string-diagrams.dtx, %% and the derived files string-diagrams.ins, %% string-diagrams.sty, and %% string-diagrams.pdf. %% %% ============================================================================= %% % % %%%=[ INSTALL ]=============================================================== % %<*internal> \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX\else \expandafter\begingroup \fi % % %<*install> \input l3docstrip.tex \keepsilent \askforoverwritefalse % NOTE: to avoid redundancy we use `%%'-lines instead of pre/postambles \preamble \endpreamble \nopostamble \usedir{tex/latex/string-diagrams} \generate{\file{\jobname.sty}{\from{\jobname.dtx}{package}}} % %<*internal> \usedir{source/latex/string-diagrams} \generate{\file{\jobname.ins}{\from{\jobname.dtx}{install}}} % % %<*internal> \ifx\fmtname\nameofplainTeX \expandafter\endbatchfile \else \expandafter\endgroup \fi % % %\endbatchfile % % %%%=[ DRIVER ]================================================================ % %<*driver> \documentclass[a4paper,full]{l3doc} \usepackage{parskip} \NewDocumentCommand{\TikZ}{}{Ti\emph{k}Z} \EnableCrossrefs \CodelineIndex \RecordChanges \usepackage{string-diagrams} \usetikzlibrary{calc} \usepackage{snapshot} \usepackage{tcolorbox} \tcbuselibrary{listings, skins} \lstset{ language=[LaTeX]TeX, basicstyle=\MacroFont, columns=flexible, ^^A keywordstyle=\color{red}, ^^A morekeywords={}, texcsstyle=*\color{violet}, moretexcs={node,wires}, breaklines=true, } \tcbset{example/.style={ listing engine=listings, verbatim ignore percent=true, listing side text, size=minimal, skin=bicolor, colback=black!5!white, colbacklower=white, sidebyside, lefthand ratio=0.62, listing options={ xleftmargin=-1.6em, % poor man's gobble=4 }, }} \begin{document} \DocInput{\jobname.dtx} \PrintChanges \PrintIndex \end{document} % % % \fi % % \changes{0.1.0}{2023/05/31}{initial version} % \changes{0.2.0}{2023/06/12}{make box ports configurable} % % \GetFileInfo{\jobname.sty} % % \title{^^A % The \pkg{\jobname} package^^A % \thanks{Thanks!}\\^^A % \fileinfo^^A % } % % \author{^^A % Paolo Brasolin\\^^A % \texttt{\href{mailto:paolo.brasolin@gmail.com}{paolo.brasolin@gmail.com}}^^A % } % % \date{\fileversion~(\filedate)} % % \maketitle % % ^^A=[ DOCUMENTATION ]========================================================= % % \begin{documentation} % % \begin{tcolorbox}[ % colback=red!5!white, % colframe=red, % sharp corners, % boxrule=1pt, % ] % Please note this is the \href{https://semver.org/#spec-item-4}{major version zero}, meant for initial development: \emph{anything MAY change at any time}. % The upside is that this is the best time to \href{https://github.com/paolobrasolin/string-diagrams#contributing}{contribute}! % Of course you can also just keep the \texttt{sty} along with your code and not care at all. % \end{tcolorbox} % % % \section{Documentation} % % \begin{function}[added=2023-05-31,updated=2023-06-12]{/pgf/box} % % To draw boxes, you use this style on a node. % % \begin{tcblisting}{example} % \begin{tikzpicture} % \node[box] {A}; % \end{tikzpicture} % \end{tcblisting} % % You can draw multiple boxes using any of your standard \TikZ\ positioning techniques. % Don't forget to label the nodes so you can easily reference them. % % \begin{tcblisting}{example} % \begin{tikzpicture} % \node[box] (A) at (0,0) {A}; % \node[box, right of=A] (B) {B}; % \node[box] (C) at ($(B)+(2cm,1em)$) {C}; % \end{tikzpicture} % \end{tcblisting} % \end{function} % % \begin{function}[added=2023-06-12]{ % /pgf/box ports north, % /pgf/box ports east, % /pgf/box ports south, % /pgf/box ports west, % } % \begin{syntax} % /pgf/box ports north=\meta{integer} % /pgf/box ports east=\meta{integer} % /pgf/box ports south=\meta{integer} % /pgf/box ports west=\meta{integer} % \end{syntax} % % You can open up any number of ports on any side of a box using the appropriate key. % Then, you can refer to the opened ports by their index. % % \begin{tcblisting}{example,lefthand ratio=0.8} % \begin{tikzpicture}[ % marker/.style={circle, fill, inner sep=1pt, text=white}, % ] % % \node[ % box, % box ports north=3, % box ports east=3, % box ports south=3, % box ports west=3, % minimum width=6em, % minimum height=6em, % ] (A) {A}; % % \foreach \side in {north,east,south,west} % \foreach \index in {1,...,3} % \node[marker] at (A.\side.\index) {\index}; % % \end{tikzpicture} % \end{tcblisting} % % \end{function} % % \begin{function}[added=2023-06-12]{ % /pgf/box ports, % } % \begin{syntax} % /pgf/box ports=\meta{integer}/\meta{integer}/\meta{integer}/\meta{integer} % \end{syntax} % % The \texttt{box ports} key is a shortcut to set the number of ports on all sides at once. % % \begin{tcblisting}{example,lefthand ratio=0.8} % \begin{tikzpicture}[ % marker/.style={circle, fill, inner sep=1pt}, % ] % % \node[box, box ports=1/2/3/4] (A) {A}; % % \foreach \side/\n in {north/1,east/2,south/3,west/4} % \foreach \index in {1,...,\n} % \node[marker] at (A.\side.\index) {}; % % \end{tikzpicture} % \end{tcblisting} % % The same value can also be passed to the \texttt{box} key itself. % % \end{function} % % \begin{function}[added=2023-05-31,updated=2023-06-13]{\wires} % \begin{syntax} % \cs{wires}\oarg{\TikZ\ keys}\marg{connectivity}\marg{loose ends} % \end{syntax} % % To connect boxes, you can use the \cmd\wires\ macro. % The first argument is \TikZ\ styling for the wires; the second argument is a nested dicionary specifying the connectivity; the third argument is a list of the loose ends to draw. % \texttt{box}es have the following anchors: \texttt{west}, \texttt{west.0}, \texttt{west.1}, \texttt{east}, \texttt{east.0}, and \texttt{east.1}. % % \begin{tcblisting}{example} % \begin{tikzpicture}[scale=0.6] % \node[box=0/2/0/1] (A) at (-2, 0) {A}; % \node[box=0/1/0/2] (B) at (+2, 0) {B}; % \node[box=0/1/0/1] (C) at ( 0,+1) {C}; % \node[box=0/1/0/1] (D) at ( 0,-1) {D}; % \wires{ % A = { east.1 = C.west, east.2 = D.west }, % C = { east = B.west.1 }, % D = { east = B.west.2 }, % }{ A.west, B.east } % \end{tikzpicture} % \end{tcblisting} % % \end{function} % % \begin{function}[added=2023-05-31]{/pgf/dot} % % To split and join wires, you can use \texttt{dot}s and their anchors \texttt{north}, \texttt{east}, \texttt{south}, and \texttt{west}. % Remember to have fun with styling wires. % % \begin{tcblisting}{example} % \begin{tikzpicture} % \node[box=0/1/0/2] (A) at ( 0,+1) {A}; % \node[box=0/2/0/1] (B) at ( 0,-1) {B}; % \node[dot] (x) at (+1, 0) {}; % \node[dot] (y) at (-1, 0) {}; % \wires[looseness=1.5, dashed]{ % A = { east = x.north }, % B = { east.1 = x.south }, % y = { north = A.west.2, south = B.west }, % }{ % A.west.1, B.east.2, x.east, y.west % } % \end{tikzpicture} % \end{tcblisting} % % \end{function} % % That's it. This is the package, for now. % % \end{documentation} % % ^^A=[ PACKAGE ]=============================================================== % % \begin{implementation} % % \section{Implementation} % % Open the \pkg{DocStrip} guards and set the internal namespace prefix (as per \LaTeX3 \pkg{DocStrip} convention). % \begin{macrocode} %<*package> %<@@=stridi> % \end{macrocode} % % Load the essential support (\pkg{expl3}) \enquote{up-front}. % % \begin{macrocode} \RequirePackage{expl3}[2023/05/11] \RequirePackage{tikz}[2023/01/15] % \end{macrocode} % % Identify the package and give the over all version information. % \begin{macrocode} \ProvidesExplPackage {string-diagrams} {2023/06/13} {0.2.1} {Draw string diagrams using TikZ} % \end{macrocode} % % \begin{macro}{ % /pgf/box ports north, % /pgf/box ports east, % /pgf/box ports south, % /pgf/box ports west, % /pgf/box ports, % } % % Define high level keys to configure the number of ports on each side. % \begin{macrocode} \pgfkeys{ /pgf/box~ports~north/.initial=1, /pgf/box~ports~east/.initial=1, /pgf/box~ports~south/.initial=1, /pgf/box~ports~west/.initial=1, /pgf/box~ports/.style~args={#1/#2/#3/#4}{ /pgf/box~ports~north=#1, /pgf/box~ports~east=#2, /pgf/box~ports~south=#3, /pgf/box~ports~west=#4, }, } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_intersect_hv_lines_through:NN} % Calculates the intersection of two lines parallel to axes passing through given points on the plane. % \begin{arguments} % \item Point through which the vertical line passes % \item Point through which the horizontal line passes % \end{arguments} % \begin{macrocode} \cs_new:Nn \@@_intersect_hv_lines_through:NN { \pgfextractx { \pgf@xa } { #1 } \pgfextracty { \pgf@ya } { #2 } \pgfpoint { \pgf@xa } { \pgf@ya } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_subdivide_segment:nNNNNN} % Defines macros numbering equally spaced points on a segment. % \begin{arguments} % \item Base namespace % \item Points count % \item Point containing the x coordinate of the starting point % \item Point containing the y coordinate of the starting point % \item Point containing the x coordinate of the ending point % \item Point containing the y coordinate of the ending point % \end{arguments} % \begin{macrocode} \cs_new:Nn \@@_subdivide_segment:nNNNNN { \int_step_inline:nnnn { #2 } { -1 } { 1 } { \cs_if_exist:cTF { #1.##1 } { \prg_break: } { \prg_do_nothing: } \cs_new_nopar:cpn { #1.##1 } { \pgfmathdivide { 2 * ##1 - 1 } { 2 * #2 } \pgfpointlineattime { \pgfmathresult } { \@@_intersect_hv_lines_through:NN { #3 } { #4 } } { \@@_intersect_hv_lines_through:NN { #5 } { #6 } } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{box} % \changes{0.2.0}{2023/06/12}{make ports configurable through \TikZ\ keys} % % Define a rectangular shape with configurable ports. % \begin{macrocode} \pgfdeclareshape{box}{ % Inherit all the structure of rectangle \inheritsavedanchors[from=rectangle] \clist_map_inline:nn { north~west, north, north~east, west, center, east, mid~west, mid, mid~east, base~west, base, base~east, south~west, south, south~east, } { \inheritanchor[from=rectangle]{#1} } \inheritanchorborder[from=rectangle] \inheritbackgroundpath[from=rectangle] % Dump port counts into saved macros \savedmacro\portsnorth {\pgfmathtruncatemacro\portsnorth{\pgfkeysvalueof{/pgf/box~ports~north}}} \savedmacro\portseast {\pgfmathtruncatemacro\portseast{\pgfkeysvalueof{/pgf/box~ports~east}}} \savedmacro\portssouth {\pgfmathtruncatemacro\portssouth{\pgfkeysvalueof{/pgf/box~ports~south}}} \savedmacro\portswest {\pgfmathtruncatemacro\portswest{\pgfkeysvalueof{/pgf/box~ports~west}}} % Add ports definitions to shape definition \expandafter\pgfutil@g@addto@macro\csname pgf@sh@s@box\endcsname{ \@@_subdivide_segment:nNNNNN { pgf@anchor@box@north } { \portsnorth } { \southwest } { \northeast } { \northeast } { \northeast } \@@_subdivide_segment:nNNNNN { pgf@anchor@box@east } { \portseast } { \northeast } { \northeast } { \northeast } { \southwest } \@@_subdivide_segment:nNNNNN { pgf@anchor@box@south } { \portssouth } { \southwest } { \southwest } { \northeast } { \southwest } \@@_subdivide_segment:nNNNNN { pgf@anchor@box@west } { \portswest } { \southwest } { \northeast } { \southwest } { \southwest } } } % \end{macrocode} % \end{macro} % % \begin{macro}{/pgf/box} % \changes{0.2.0}{2023/06/12}{acts as a shortcut for setting port counts} % % Define style to draw boxes. % \begin{macrocode} \ExplSyntaxOff \tikzset{ box/.default={0/0/0/0}, box/.style args={#1}{ shape=box, draw, inner sep=.5em, minimum width=2em, minimum height=2em, execute at begin node=$, execute at end node=$, /pgf/box ports=#1, }, } \ExplSyntaxOn % \end{macrocode} % \end{macro} % % \begin{macro}{/pgf/dot} % % Define style to draw dots. % \begin{macrocode} \ExplSyntaxOff \tikzset{ dot/.style={ shape=circle, fill, inner sep=0, minimum width=0.4em, }, } \ExplSyntaxOn % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_draw_bound_wires:nn} % Draws bound wires. % \begin{arguments} % \item \TikZ\ keys % \item dictionary of port labels % \end{arguments} % \begin{macrocode} \cs_new:Nn \@@_draw_bound_wires:nn { \prop_set_from_keyval:Nn \l_tmpa_prop { #2 } \prop_map_inline:Nn \l_tmpa_prop { \prop_set_from_keyval:Nn \l_tmpb_prop { ##2 } \prop_map_inline:Nn \l_tmpb_prop { \regex_match_case:nn { { \. north } { \tl_gset:Nn \g_tmpa_tl { 90 } } { \. south } { \tl_gset:Nn \g_tmpa_tl { -90 } } { \. west } { \tl_gset:Nn \g_tmpa_tl { 180 } } { \. east } { \tl_gset:Nn \g_tmpa_tl { 0 } } } { ####2 } \regex_match_case:nn { { north } { \tl_gset:Nn \g_tmpb_tl { 90 } } { south } { \tl_gset:Nn \g_tmpb_tl { -90 } } { west } { \tl_gset:Nn \g_tmpb_tl { 180 } } { east } { \tl_gset:Nn \g_tmpb_tl { 0 } } } { ####1 } \draw [ out={\tl_use:N \g_tmpb_tl}, in={\tl_use:N \g_tmpa_tl}, #1, ] (##1.####1) to (####2); } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_draw_loose_wires:nn} % Draws loose wires. % \begin{arguments} % \item \TikZ\ keys % \item list of port labels % \end{arguments} % \begin{macrocode} \cs_new:Nn \@@_draw_loose_wires:nn { \clist_set:Nn \l_tmpa_clist { #2 } \clist_map_inline:Nn \l_tmpa_clist { \regex_match_case:nn { { \. north } { \draw[#1] (##1) -- +( 0,+1); } % TODO: cleaner solution? { \. south } { \draw[out=-90, in=0,#1] (##1) to ($(\pgf@picminx, \pgf@y)$); } % TODO: not sure why this works { \. west } { \draw[#1] (##1) -- +(-1, 0); } { \. east } { \draw[#1] (##1) -- +(+1, 0); } } { ##1 } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\wires} % \changes{0.2.1}{2023/06/13}{now correctly handles optional style parameter} % Define our main actor. % \begin{macrocode} \NewDocumentCommand{\wires}{ O{} m m } { \@@_draw_bound_wires:nn { #1 } { #2 } \@@_draw_loose_wires:nn { #1 } { #3 } } % \end{macrocode} % \end{macro} % % Close the \pkg{DocStrip} guards and call it a day. % \begin{macrocode} % % \end{macrocode} % % \end{implementation}