2026-07-18 18:48:23 +02:00
|
|
|
\usepackage{etoolbox}
|
|
|
|
|
\usepackage{fancyvrb}
|
|
|
|
|
|
|
|
|
|
\usepackage{listings}
|
|
|
|
|
\lstset{basicstyle=\ttfamily,fontadjust=true,basewidth=0.5em,xleftmargin=26pt}
|
|
|
|
|
|
|
|
|
|
\usepackage[strings,nohyphen]{underscore}
|
|
|
|
|
|
|
|
|
|
\usepackage{mdframed}
|
Option sets: a .o target for shared parameters
A named group of optional parameters, declared once and used by several
klammers, so a writer learns one vocabulary instead of a spelling per
klammer. The "o" target is a pseudo-target beside "k": "k" declares a
klammer's interface and documents it, "o" declares an option interface and
documents it, and neither produces output for any target.
@@caption_args.o :caption :number.bool true :caption_side.side
: Arguments that define a caption for a block element @@
@@code.k :filename @hpos_args :hpos left @ @caption_args :caption_side top @
| text.literal : A source file displayed verbatim @@
A set is used only in the parameter list of a ".k" declaration -- the one
place a klammer's interface is declared once for all of its targets -- and
is resolved as that list is read. Names and types come from the set; a
default may be overridden where it is used. A klammer application in a
parameter list is now a definition-time error.
The SKS gains the sets caption_args and hpos_args (:hpos and :offset), and
@table, @image, @image_grid, @reference and @show gain .k declarations. A
distance is no longer written as a position: :hpos 4em is rejected, and the
same layout is :hpos left :offset 4em. Code listings are numbered by
default, like tables and figures.
New engine sources mac/option_set{,_registry}.{h,cpp}; tst/ ships two more
suites, option_set_test.sh and signature_test.sh (twelve in all).
(from dev 34e536cb0329)
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}}
|