4 TEI, and the facsimile rebuilt from it
racket main.rkt --xslt -o out samples/hamlet.txt |
writes out/hamlet.tei.xml and a plain reading text beside it.
The important arrangement is the other way round. –html does not render the book a second time: it reads the .tei.xml back off disk and builds the type-facsimile from that and nothing else. So the TEI is the record and everything else is derived from it, and anything the TEI does not carry cannot appear on the page.
That is a property rather than a discipline, and it earns its keep. There were once two renderers — one from the book in memory, one from the TEI — and they drifted, because each knew things the other did not; a parity test between them caught none of it. Reading from the file instead has already exposed four things the encoding was quietly missing: the identity of the damaged sorts, the statistics, the stage-by-stage account of what happened to each word, and the space-metal. Each had reached the page by some route that did not pass through the file, so the file never had to carry it.
The XSLT survives as a deliberately smaller thing: a reading text, giving the words and the page breaks and the reading rather than the glyph — <reg> not <orig> — which is the half of every <choice> the facsimile does not show. It makes no claim to match the facsimile, and there is a test that it does not try.
procedure
(book->tei b [run names]) → string?
b : book? run : (or/c press-run? #f) = #f names : (listof string?) = '("A" "B")
TEI already has the vocabulary and most projects never use it: <fw> is forme work — running titles, signatures, catchwords; <pb/>, <cb/>, <lb/> are page, column and type line; <lb break="no"/> is a word divided across a line; <choice> with <abbr>/<expan> is a compositor’s contraction and with <sic>/<corr> a literal; and <app> with <rdg wit="…"> is a critical apparatus, which is exactly what a list of press variants across copies of one edition is. Compositors are <respStmt>s and every page carries @resp; the causes of variation are a <taxonomy> in the header that every word points into with @ana.
Two decisions worth arguing with.
Milestones, not containers. Verse lines, speeches and typographic lines overlap constantly here: a turned-over verse line is one verse line across two type lines, and a prose paragraph runs over a page break. XML cannot nest overlapping hierarchies, and TEI’s own answer is the milestone. So <lb/> is empty and the words are its siblings. The cost is that <l> does not appear; the benefit is that nothing is misrepresented, and the XSLT can group words by sibling axis — which XSLT 1.0 can actually do.
Geometry in a foreign namespace. The point of the program is that the justification is the compositor’s and not the browser’s, so the computed position of every word must survive into the output. But an em offset is process data, not text, and has no business wearing TEI semantics. It goes in the hp: namespace, the standard escape hatch; strip that one namespace and you lose nothing textual.
4.1 Leaves, openings, and which side you are looking at
The XSLT rendering lays the book out in openings: the verso of one leaf on the left and the recto of the next on the right, as the book is held. The first recto therefore stands alone on the right of the first opening, with a dashed placeholder where the outside of the book would be, and a final verso stands alone on the left. Every leaf is given the full depth of a page, because a leaf is a fixed piece of paper however little type stands on it.
Under each page is its signature, the leaf it belongs to, and whether it is the recto or the verso.
–layout leaf pairs the two sides of one leaf instead, recto then verso. That is not a view anyone ever has of a bound book — the two sides of a leaf cannot be seen at once — but it is the view the compositor and the pressman had, and it puts the two formes of a leaf where they can be compared. Both are offered because “show the leaves with the pages side by side” can reasonably mean either.
4.2 Fitting the face to the body
The rendering is scaled by one number. A pica em is 4.2175 mm and is drawn –grid pixels wide, so –mm — one millimetre in pixels — follows from it, and the type, the leaf and the margins are all measured in the same millimetres and move together.
–grid |
| one em of the type body, in pixels |
–mm |
| one millimetre, derived: --grid ÷ 4.2175 |
–fit |
| the set width of the face against that body |
–lead |
| line pitch as a multiple of the body |
–leaf-h, –leaf-w |
| the uncut leaf in mm, from the file |
–mi, –mh, –mo, –mt |
| inner, head, outer and tail margins in mm |
The leaf dimensions and the margins are read out of the TEI, not computed here. The stylesheet used to build a leaf from the type page plus eleven ems of margin it had chosen itself, which is a second place deciding a thing the model should own — and it drew a quarto half again too tall. Now the sheet decides the size, the file carries it, and the stylesheet only scales it.
–lead is 1.00 because the description says the type is set solid, and set solid means the line pitch is the body. It was 1.44, a screen line-height with nothing behind it, which inflated the type page by 44%.
Two bugs surfaced the moment the paper became authoritative, both of which the old arrangement had concealed by feeding the leaf and its contents the same wrong number. –lines is the lines on the page, which is what a bibliographer counts — a two-column folio of 66-line columns has 132 — so the depth of a column is –lines divided among the columns and not –lines itself. And the gutter between columns had to go into the modelled type page, because without it the flex box squeezed the columns to fit, and a column that is squeezed does not reflow: every word in it sits at an absolute offset the compositor computed, so it clips.
–grid and –fit have to be separate. Every word is positioned at calc(var(–grid) * var(–x)), where –x is the offset the simulation computed. If the position were expressed in em instead, it would resolve against the word’s own font-size, so the glyphs and the grid would scale together and a wide face could never be made to fit — which is exactly the fault the first version had: 222 pairs of words overlapped, some by six pixels, and the word-spaces vanished entirely.
The default stack is Times-like, because an old-face roman is narrow and Georgia and Palatino are not. Calibrated by measuring every word in the rendered page, –fit: 1.00 puts the median word within 1% of its modelled width and brings the median gap between words to 5.4px against a true thick space of 5.33px. So the white you see between two words is the space the compositor put there. If you substitute a wider face, lower –fit until no words touch.
4.3 Running the transform
The stylesheet is XSLT 1.0, because the processor most likely to be present without an install is .NET’s XslCompiledTransform. apply-xslt prefers xsltproc if it is on the PATH and otherwise calls tools/xslt.ps1.
procedure
(apply-xslt xml xsl out [#:witness witness]) → boolean?
xml : path? xsl : path? out : path? witness : string? = "copya"
The witness parameter chooses which made-up copy of the edition the facsimile shows. Since the copies were gathered at random from the heaps they disagree, so –witness copya and –witness copyb produce genuinely different pages from the same TEI — which is the point of an apparatus.