1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
// colitur wall calendar -- A4 landscape, one month per page. Typst. flavour: typst
// Build: colitur table --year 2027 --template grid.typ > grid.typ && typst compile grid.typ
// ONE compile pass: this template has no table of contents and no
// cross-references (unlike ordo.typ), so there is nothing for a second pass
// to resolve even in principle -- unlike pdflatex's own two-pass need for
// grid.tex's sibling ordo.tex, though grid.tex itself already needed only
// one pass too.
//
// The grid must FILL the page, not sit in its top quarter -- AND every
// month must fit on exactly ONE page, never spill onto a second (Defect 3;
// this file used to read `rows: (auto, 1fr)`, described below as filling
// the page "completely regardless of 5 or 6 rows"). That description was
// true for row COUNT but silently assumed a fixed CONTENT height per row
// too: adding the sigla line grew each cell's natural content past what a
// 1/6-of-remaining-space `1fr` row allows, and a Typst `fr` track's size is
// the LARGER of its fair share and its content's own minimum -- it grows
// PAST 1fr rather than shrinking content to fit, which is exactly how a
// 6-row month (`I classis` sliced mid-cell, the next month's own heading
// printed underneath it) spilled onto a second page. Fixed now the same
// way grid.tex's own \cellh already was: an EXPLICIT row height
// ([cellh] below, computed from the page's own text height divided by six
// -- every month in the 1583-9999 domain has 5 or 6 Sunday-started weeks,
// never more), with each cell's own content wrapped in a height-clipped
// block sized to match. A 5-week month still leaves a blank sixth row (the
// same trade-off grid.tex's own header already discloses and defends);
// the disclosed DIVERGENCE this comment used to describe -- Typst
// stretching every month to fill the page regardless of row count -- is
// GONE: overflow safety now matters more than that cosmetic fill, and a
// fixed height is what makes the fit provable rather than hoped for.
//
// Real names, not slugs: the observed day's own display name is a PLAIN
// resolved string (View.of_days), interpolated directly as a plain var
// below -- there is no dotted-la-with-slug-fallback idiom left to write
// (see ordo.typ's own header for the full reasoning; it applies here
// unchanged).
//
// The weekday header row comes from the view's top-level weekday_headings
// list (name/last objects), not a hard-coded Dom/Lun/Mar row: the engine
// rejects an EMPTY tag path -- a bare dot inside a section, on its own --
// as a parse error, so the loop below names a field on each heading object
// rather than interpolating the section's own value directly, and a
// hard-coded row would not be localised anyway.
//
// No separator flag needed: grid.tex's own `last` field exists because a
// LaTeX tabular row needs its ampersand BETWEEN cells, never after the
// final one, and the engine has no "unless last" construct to lean on
// instead -- so that template renders the flag as data. Typst's own
// #table() takes a flat, comma-separated argument list and wraps it into
// rows itself, by column count, so the SAME rule applies to every cell,
// header row included, with no special case for the last one -- verified
// live that a trailing comma before a function call's closing parenthesis
// is accepted, not a syntax error. `last` is therefore simply unused here;
// left in the view model for grid.tex's own sake, not removed for this one.
//
// Sigla, compactly: each cell also carries the day's Epistle and Gospel
// references (view fields first/gospel), joined by a centred dot rather
// than repeating Ep./Ev. labels -- the same choice grid.tex and grid.ms
// already made, kept consistent rather than invented a second way. A
// #v(1fr) inside each cell's own content -- verified live to work the same
// way inside a table cell as inside any other block -- pushes the rank
// line and the sigla to the bottom of the cell, leaving the day number and
// name at the top, so the two short lines never collide with a long entry
// above them the way a single crammed line would.
#set page(paper: "a4", flipped: true, margin: 10mm, numbering: none)
#set text(size: 8pt)
#set par(justify: false)
#let cgrid = (
white: rgb("#FFFFFF"), red: rgb("#F8DCDC"), green: rgb("#DDEEDD"),
violet: rgb("#E6DAF0"), rose: rgb("#FADCE8"), black: rgb("#DCDCDC"),
)
#let cpad = luma(248)
// a4 landscape text area is 277x190mm at this page's own 10mm margin.
// [titleh] is the measured height of the month title line + its own
// #v below (16pt bold, one line, verified against real rendered output,
// not assumed); [headerh] the weekday-name row. What is left
// (190 - titleh - headerh) is divided by SIX, never five, so a 6-row
// month is what this budget is proven against -- a 5-row month then has
// one row's worth of genuine spare room, not a tight fit. [cellinset] is
// subtracted back out per side when sizing each cell's own clipped inner
// block below.
// Budgeted against 184mm, not the full 190mm text height: a first attempt
// at exactly 190mm (zero slack) measurably split the table BETWEEN rows
// on real output -- Typst's own table breaks across a page boundary by
// default when the sum of explicit row heights doesn't quite clear the
// remaining space, stranding the 6th row alone on its own page with no
// heading -- so 6mm of genuine slack is deliberate, not rounded for
// looks. `breakable: false` below is the second, independent guard: even
// if this budget is ever wrong again, the WHOLE table jumps to a fresh
// page rather than splitting a row across two -- loudly wrong (an odd
// blank page) rather than silently wrong (a sliced row). It is a #block
// property, not a #table one (Typst rejects `table(breakable: ...)`
// outright as an unexpected argument -- verified live), so the table
// below is wrapped in a block rather than passed the flag directly.
//
// Each cell's own content sits in a SECOND, height-clipped block, exactly
// [cellh] minus its own top/bottom inset: the table row height is
// EXPLICIT ([cellh], not `auto` or `1fr`), so nothing inside can grow the
// row past it -- content that still does not fit is clipped rather than
// spilling into the next cell or the next page, the "truncate
// gracefully" option Defect 3's own brief names. Never actually hit on
// real 1583-9999 data (checked against the full 2027 fixture, every
// month, both row counts -- see the task report), but the box is
// provably safe either way, which a content-driven row never was. Inside
// that block, one Typst paragraph throughout, a trailing backslash
// between the rank line and the sigla line rather than a bare newline:
// two markup lines joined only by a bare newline stay on ONE rendered
// line (still the same soft-wrapped paragraph) -- verified live, the
// first draft of this cell merged "III classis" straight into the
// citation. grid.tex's own header explains why the rank line and the
// sigla get their own line EACH rather than being crammed onto one -- the
// same reasoning applies here, so this keeps the two-line shape it
// settled on.
#let titleh = 9mm
#let headerh = 6mm
#let cellh = (184mm - titleh - headerh) / 6
#let cellinset = 1.2mm
{{#months}}
#text(size: 16pt, weight: "bold")[{{name}} {{year}}]
#v(1.5mm)
#block(breakable: false)[
#table(
columns: (1fr,) * 7,
rows: (headerh, cellh),
stroke: 0.4pt + luma(150),
inset: cellinset,
align: left + top,
{{#weekday_headings}}
table.cell(align: center + horizon)[#text(size: 7pt, weight: "bold")[{{name}}]],
{{/weekday_headings}}
{{#weeks}}
{{#days}}
{{#in_month}}
table.cell(fill: cgrid.at("{{colour}}"))[
#block(height: cellh - 2 * cellinset, clip: true)[
#set par(leading: 0.3em)
#text(weight: "bold")[{{dom}}] #text(size: 6.5pt)[{{name}}]
#v(1fr)
#text(size: 6pt)[{{rank_name}}] \
#text(size: 6pt)[{{#first}}{{first}}{{/first}}{{#gospel}}{{#first}} #sym.dot.c {{/first}}{{gospel}}{{/gospel}}]
]
],
{{/in_month}}
{{^in_month}}
table.cell(fill: cpad)[],
{{/in_month}}
{{/days}}
{{/weeks}}
)
]
#pagebreak(weak: true)
{{/months}}
|