Files

175 lines
7.6 KiB
Plaintext
Raw Permalink Normal View History

\usepackage{etoolbox}
\usepackage{fancyvrb}
\usepackage{listings}
\lstset{basicstyle=\ttfamily,fontadjust=true,basewidth=0.5em,xleftmargin=26pt}
\usepackage[strings,nohyphen]{underscore}
\usepackage{mdframed}
2026-08-06 13:11:37 +02:00
\newsavebox{\measurebox}
\newlength{\widestline}
% \measurewidest{line one\\line two\\...} -> \widestline = width of widest
\newcommand{\measurewidest}[1]{%
\begin{lrbox}{\measurebox}%
\begin{tabular}{@{}l@{}}#1\end{tabular}%
\end{lrbox}%
\setlength{\widestline}{\wd\measurebox}%
}
% \asymbox[<left pad>]{line one\\line two\\...} other three sides: 8pt
\newcommand{\asymbox}[2][0pt]{%
\begin{lrbox}{\measurebox}%
\begin{tabular}{@{}l@{}}\tt #2\end{tabular}%
\end{lrbox}%
{\setlength{\fboxsep}{0pt}%
\fcolorbox{black}{yellow!20}{%
\kern#1%
\vbox{\kern0pt\hbox{\usebox{\measurebox}}\kern0pt}%
\kern0pt}}%
}
% ===========================================================================
% Annotated code listings. Emitted by sks/code/code_block.py; the layout
% policy lives here so it can be tuned without touching the Python.
%
% The model: a listing is a vertical stack of BLOCKS, each block a run of
% code lines with an optional comment beside it. There is deliberately no
% table -- comments are a fixed distance from their own block and do not
% align with each other, and each block's shaded box is as wide as that
% block's longest line.
%
% Two mechanisms carry the whole appearance:
%
% 1. Every line is a \strut'ed \makebox of the block's width. A LaTeX
% \strut is exactly 0.7\baselineskip high and 0.3 deep -- one full
% \baselineskip -- so consecutive lines inside a block abut exactly,
% with no gap and no overlap, at any point size. Without the strut a
% line with no descender would sit closer to its neighbour and the
% rhythm would follow the text rather than the grid.
%
% 2. \offinterlineskip in the listing removes the interline glue BETWEEN
% BLOCKS, so consecutive blocks abut too and a boxed block continues
% the rhythm of the plain lines around it. (It does not reach inside
% a \parbox, which restores normal interline spacing -- that is why
% the struts in point 1 are doing the work there, and why the box's
% vertical padding comes from \fboxsep below rather than from a
% padding line, which would be spaced off the grid.)
% ===========================================================================
\definecolor{klcodeshade}{rgb}{1,1,.6}
\newlength{\kllistingwidth} % width of a whole unannotated listing
% How the listing is placed WITHOUT being boxed, so that it can still break
% across pages: \klshift moves it right (an :offset with :hpos left) and
% \klindent is the width taken out of \linewidth (either side's offset).
% A minipage would do both at once but cannot break -- see Code.tex in
% sks/code/code_block.py, which sets these before \begin{klcode}.
\newlength{\klshift} \setlength{\klshift}{0pt}
\newlength{\klindent} \setlength{\klindent}{0pt}
\newlength{\klcodewidth} % width of the current block's box
\newlength{\klcommentwidth} % what is left for its comment
% Horizontal inset of the code from the box's left edge. 0pt puts the
% box's left edge ON the code column, where the line's own indentation
% begins, rather than out to the left of it; a positive value moves the
% CODE right, never the box left.
\newlength{\klcodepad} \setlength{\klcodepad}{0pt}
\newlength{\klcodegap} \setlength{\klcodegap}{14pt} % box to comment
\newlength{\klcodemin} \setlength{\klcodemin}{6em} % narrower: see below
% Clear space above and below a block that HAS a box, so that two boxed
% blocks with no unboxed lines between them read as two boxes rather than
% one. Applied per block, never per line: the lines within a block must
% still abut, or the shading stops being a solid rectangle.
% 1.5pt is 2px at the CSS reference 96dpi, matching the html margin in
% css/code.css -- these two are meant to look the same, so change them
% together.
\newlength{\klboxgap} \setlength{\klboxgap}{1.5pt}
% The box's own vertical margin: space INSIDE the shading, above the first
% line and below the last. Same 2px equivalent, matching the html padding.
\newlength{\klboxpad} \setlength{\klboxpad}{1.5pt}
% \klline{<line>} — one code line, padded and strut'ed to the block width.
\newcommand{\klline}[1]{%
\makebox[\klcodewidth][l]{\hspace{\klcodepad}\strut\ttfamily #1}}
% \klcodebox{<lines>} — the block's lines, shaded when the block has a
% comment (\klcomment is set by \klblock).
%
% The shading is ONE \colorbox around the whole block, not one per line,
% because only a box around the whole block can have a vertical margin.
%
% That margin is \vspace* INSIDE the \parbox rather than \fboxsep: \fboxsep
% pads all four sides, and any horizontal padding would put the box's left
% edge out to the LEFT of the code column. With \fboxsep 0 the box spans
% exactly the code's own extent, so it begins where the line's indentation
% begins and the code stays aligned with the unshaded lines around it.
\newcommand{\klcodebox}[1]{%
\ifx\klcomment\empty
\parbox[c]{\klcodewidth}{#1}%
\else
\setlength{\fboxsep}{0pt}%
\colorbox{klcodeshade}{%
\parbox[c]{\klcodewidth}{\vspace*{\klboxpad}#1\vspace*{\klboxpad}}}%
\fi}
% \klblock{<widest line>}{<lines>}{<comment>}
% <lines> is \klline/\klshaded calls separated by \\; <comment> may be
% empty. The comment gets what the box leaves of \linewidth (NOT
% \textwidth: inside a list or minipage they differ, and \textwidth would
% push the comment into the margin). If that remainder is too narrow to
% set prose in, the comment goes BELOW the block rather than being
% squeezed into an overfull box.
\newcommand{\klblock}[3]{%
\begingroup
% A block is boxed exactly when it has a comment, so this one test also
% decides whether the block gets the \klboxgap separation.
\def\klcomment{#3}%
\ifx\klcomment\empty\else\vskip\klboxgap\fi
\settowidth{\klcodewidth}{\ttfamily #1}%
\addtolength{\klcodewidth}{2\klcodepad}%
% The box advances the line by exactly \klcodewidth (\fboxsep is 0 in
% \klcodebox). If a horizontal padding is ever reintroduced there, its
% width must be subtracted here too: leaving it out overfills the line,
% and \raggedright then breaks it -- dropping the comment onto the next
% line at the margin, which looks like the narrow-comment fallback.
\setlength{\klcommentwidth}%
{\dimexpr\linewidth-\klindent-\klcodewidth-\klcodegap\relax}%
\noindent
\ifdim\klcommentwidth<\klcodemin
\klcodebox{#2}%
\ifx\klcomment\empty\else
\\\parbox[t]{\linewidth}{\klcommentfont #3}%
\fi
\else
\klcodebox{#2}%
\ifx\klcomment\empty\else
\hspace{\klcodegap}%
\parbox[c]{\klcommentwidth}{\klcommentfont #3}%
\fi
\fi
% End the block's line. Without this every block joins ONE horizontal
% list and the blocks are broken into lines and justified like words.
\par
\ifx\klcomment\empty\else\vskip\klboxgap\fi
\endgroup}
% The annotation font. Ragged right: a justified annotation beside a
% narrow box hyphenates badly.
\newcommand{\klcommentfont}{\sffamily\itshape\small\raggedright}
% \begin{klcode} ... \end{klcode} — the listing itself. \offinterlineskip
% is what makes the struts the only thing setting vertical rhythm.
\newenvironment{klcode}
{\par\addvspace{0.5\baselineskip}%
\begingroup
\setlength{\parindent}{0pt}%
\setlength{\parskip}{0pt}%
\raggedright
% An unboxed listing is shifted with \leftskip rather than wrapped in a
% minipage, so that it can still break across pages.
\leftskip=\klshift
\offinterlineskip}
{\endgroup\par\addvspace{0.5\baselineskip}}