Files
klammertext/sks/tns/markdown.css
Andy Kopra 4a4c7404cc Markdown to PDF: the mdpdf command, and the fonts it needs
This snapshot carries sks/tns/, the translation directory, into the
distribution for the first time, together with the two default font
families its stylesheet names.

sks/tns/ holds two converters in opposite directions.  md_to_sks.py
converts Markdown to Klammertext, recording what it cannot convert exactly
as "#[MD ... ]#" markers so a draft carries its own worklist.  md_to_pdf.py
renders Markdown straight to PDF through a headless Chromium driven over the
DevTools Protocol, bypassing Klammertext entirely -- the route for a
document that is not ready to convert, and a permanent one for Markdown that
Klammertext cannot represent well.  Neither is loaded by the SKS; md_to_pdf
needs markdown-it-py, which it keeps in a virtual environment of its own and
creates with --setup.

The everyday form of the second is the mdpdf command, a shell function in
sks/tns/mdpdf.sh that env/runtime.env sources, so anyone with the
Klammertext environment has it:

    mdpdf notes.md          # writes notes.pdf beside it

It supplies the house fonts, the size matching, and the code wrapping,
completes on *.md at the TAB key, and takes its defaults from MDPDF_*
variables so one can be changed in a shell profile without copying the
function.  It is POSIX shell rather than zsh, since runtime.env is sourced
from bash profiles too.

Two things the stylesheet does that a print stylesheet usually cannot.  Code
lines are wrapped to a column count MEASURED from the rendered page rather
than written down -- the browser is asked how many characters a code box
holds, over every box in the document, so the wrapping stays right when the
fonts, sizes or margins change.  And the page number is a CSS Paged Media
margin box, which current Chromium implements, so it is set in the
document's own face instead of the browser's generic sans.

fnt/ gains EB Garamond and Source Sans 3, the serif and sans the stylesheet
asks for by default.

 (from dev 97d4f244c737)
2026-08-09 15:54:05 +02:00

234 lines
8.4 KiB
CSS

body {
font-family: var(--serif);
font-size: 100%;
line-height: 1.3;
}
/* The monospace font is scaled so its letters are the height of the serif
font's, rather than by eye: md_to_pdf.py computes --mono-scale from the
two fonts' x-height and cap-height (the same rule the SKS applies), so
this stays right if either font is changed. */
code, pre {
font-family: var(--mono);
font-size: calc(1em * var(--mono-scale, 1));
}
/* Once, not twice. A fenced block is <pre><code>, so both elements match
the rule above and the scale is applied to the scaled size: measured
before this rule, inline code came out at 14.00px and the same code in a
box at 12.26px -- a metric-matched scale silently defeating itself. The
code in a box inherits the pre's size instead. */
pre code {
font-size: 1em;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--sans);
font-weight: normal;
padding-top: .5rem;
padding-bottom: 0rem;
margin-top: .0rem;
margin-bottom: 0rem;
}
h1 {
font-size: 1.2rem;
}
h2 {
font-size: 1.1rem;
}
h3, h4, h5, h6 {
font-size: 1.0rem;
}
/* The gap between a section number and its title, in a heading and in a
table-of-contents entry alike. The number is plain text in the Markdown
("## 2.9 Keystone correction", "- [2.9 Keystone correction](#29-...)");
md_to_pdf.py wraps it in this span and eats the space that followed it, so
the whole distance is set here. Change --secnum-gap to widen or narrow
it, or give the contents its own gap with a "li .secnum" rule. To rule a
contents into columns rather than merely space it, add
"display: inline-block" and a "min-width" there: the titles then align
instead of each starting one gap after a number of its own width. */
:root {
--secnum-gap: 0.5em;
}
.secnum {
margin-right: var(--secnum-gap);
}
p, ol, ul {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
pre {
/* A code line wider than the page does not overflow or clip when
printing: Chromium shrinks the WHOLE document until it fits, silently,
by a factor that changes with the longest line. Wrapping guarantees
that never happens. md_to_pdf.py --wrap-code breaks shell lines at a
word boundary with a "\" first, which is copy-pasteable; this catches
what it cannot wrap (PowerShell, .ini, ASCII diagrams). */
white-space: pre-wrap;
overflow-wrap: anywhere;
padding-left: 0rem;
border: 1px solid #888;
background-color: #EFE;
}
th {
border-bottom: 1px solid #000;
}
table, th, td {
border: 1px solid gray;
}
@media print {
/* A section title never sits alone at the foot of a page: the break is
forbidden between a heading and whatever follows it, so the heading
moves to the next page with its text. break-inside keeps a heading
that wraps from being split across the fold.
Chromium honours these when it paginates for printing (measured on a
16-page test document: three headings stranded at a page bottom
before, none after, and the page count unchanged). A heading it
cannot honour them for -- one taller than the page -- is laid out
as if they were absent rather than looped over, which is the
specified behaviour for an unsatisfiable avoid.
This does NOT govern how much of the following paragraph comes with
the heading: that is `orphans`, below. */
h1, h2, h3, h4, h5, h6 {
break-after: avoid;
break-inside: avoid;
}
/* A paragraph split by a page break leaves at least two lines on each
side of it: never one line stranded at the foot of a page (orphans),
never one line arriving alone at the top of the next (widows) -- and
with break-after above, that top-of-page line would be the one
directly under a heading the break has just moved. Both properties
are inherited, so declaring them on body covers list items and table
cells too.
Measured on a 16-page test document: Chromium honours both -- with
orphans: 1 it packs the text into 15 pages, and with widows: 1 it
moves a line back across a page boundary (same page count, different
pagination) -- and 2 is ALREADY its default for both, so rendering
with these declarations is byte-identical to rendering without them.
They are here to pin the values, not to change today's output: the CSS
initial value is 2 but a UA is free to differ, and a stylesheet added
after this one could relax it. */
body {
orphans: 2;
widows: 2;
}
pre {
padding: .75rem 1.25rem .75rem 1.25rem;
break-inside: avoid;
}
a[href]:after {
content: none;
}
th, td {
padding: .25rem .75rem;
}
table {
margin-left: 1rem;
margin-bottom: .5rem;
border-collapse: collapse;
}
th {
border-bottom: 1px solid #000;
text-align: left;
padding-right: 2rem;
padding-bottom: 0.2em;
}
tbody tr:first-child td {
padding-top: 0.0em;
}
blockquote {
margin-left: 1rem;
}
table, th, td {
border: 1px solid #BBB;
}
:root {
--link-blue: #042B8C; /* #03216C; */
}
a {
color: var(--link-blue);
}
a, a:link, a:visited {
outline: 0;
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-underline-offset: 0.2em;
text-decoration-thickness: 1px;
outline: 0;
}
/* The page number, in the document's own face.
This is a CSS Paged Media margin box, and it is the reason to prefer
it over md_to_pdf.py's --page-numbers: Chromium's footer template is
rendered in a context of its own that cannot see this stylesheet, so
the number comes out in a generic sans, while var(--serif) here is the
EB Garamond the text is set in. Measured 2026-08-09 on Brave 151:
margin boxes work and count pages. They are recent, though -- an
older Chromium silently prints no number at all.
DO NOT ALSO PASS --page-numbers. The two mechanisms are independent
and both would draw, one over the other, and the flag additionally
widens the bottom margin.
HEIGHT. A margin box is drawn in the margin and cannot be moved out
of it, so where the number sits and how much text a page holds are
one question, not two. Chromium centres the box in the margin band
by default; these declarations pin it to the top of a slightly larger
band instead, which puts the number 27pt above the paper edge rather
than 12 -- a body line higher -- while the padding, all 1.5pt of it,
leaves the text block ending just above it. The text therefore keeps
the line it would otherwise have lost to the raised number: measured,
a document that grew from 3 pages to 4 when the number was raised
alone is back to 3.
THE TIGHT SPOT, and the number to change if it shows. Text and
number now share a narrow band, so on a page whose last line reaches
the bottom of the block they nearly touch: measured worst pages over
three documents, 7.6 / 3.1 / 2.4pt between the last line's box and
the number's. Nothing overlapped, but 2.4pt is close. Giving the
text back only HALF a line -- margin-bottom 0.60in, padding-top
4.4pt, the number within a point of where it is now -- measured
8.4 / 9.1 / 11.4pt instead, and cost no document a page.
var(--serif) is safe here because the body is set in it, so the
webfont is loaded. A face the document does not otherwise use is NOT
loaded for a margin box: var(--mono) in a prose-only document fell
back to the platform monospace, and in one variant the box rendered
nothing whatever -- silently, as ever with print CSS. */
@page {
margin-bottom: 0.56in; /* the number, and little else */
@bottom-center {
content: counter(page) " of " counter(pages);
font-family: var(--serif);
font-size: 9pt;
color: black;
vertical-align: top; /* not centred in the margin band */
padding-top: 1.5pt; /* its distance from the text */
}
}
}