// Simple numbering for non-book documents #let equation-numbering = "(1)" #let callout-numbering = "1" #let subfloat-numbering(n-super, subfloat-idx) = { numbering("1a", n-super, subfloat-idx) } // Theorem configuration for theorion // Simple numbering for non-book documents (no heading inheritance) #let theorem-inherited-levels = 0 // Theorem numbering format (can be overridden by extensions for appendix support) // This function returns the numbering pattern to use #let theorem-numbering(loc) = "1.1" // Default theorem render function #let theorem-render(prefix: none, title: "", full-title: auto, body) = { if full-title != "" and full-title != auto and full-title != none { strong[#full-title.] h(0.5em) } body } // Some definitions presupposed by pandoc's typst output. #let content-to-string(content) = { if content.has("text") { content.text } else if content.has("children") { content.children.map(content-to-string).join("") } else if content.has("body") { content-to-string(content.body) } else if content == [ ] { " " } } #let horizontalrule = line(start: (25%,0%), end: (75%,0%)) #let endnote(num, contents) = [ #stack(dir: ltr, spacing: 3pt, super[#num], contents) ] #show terms.item: it => block(breakable: false)[ #text(weight: "bold")[#it.term] #block(inset: (left: 1.5em, top: -0.4em))[#it.description] ] // Some quarto-specific definitions. #show raw.where(block: true): set block( fill: luma(230), width: 100%, inset: 8pt, radius: 2pt ) #let block_with_new_content(old_block, new_content) = { let fields = old_block.fields() let _ = fields.remove("body") if fields.at("below", default: none) != none { // TODO: this is a hack because below is a "synthesized element" // according to the experts in the typst discord... fields.below = fields.below.abs } block.with(..fields)(new_content) } #let empty(v) = { if type(v) == str { // two dollar signs here because we're technically inside // a Pandoc template :grimace: v.matches(regex("^\\s*$")).at(0, default: none) != none } else if type(v) == content { if v.at("text", default: none) != none { return empty(v.text) } for child in v.at("children", default: ()) { if not empty(child) { return false } } return true } } // Subfloats // This is a technique that we adapted from https://github.com/tingerrr/subpar/ #let quartosubfloatcounter = counter("quartosubfloatcounter") #let quarto_super( kind: str, caption: none, label: none, supplement: str, position: none, subcapnumbering: "(a)", body, ) = { context { let figcounter = counter(figure.where(kind: kind)) let n-super = figcounter.get().first() + 1 set figure.caption(position: position) [#figure( kind: kind, supplement: supplement, caption: caption, { show figure.where(kind: kind): set figure(numbering: _ => { let subfloat-idx = quartosubfloatcounter.get().first() + 1 subfloat-numbering(n-super, subfloat-idx) }) show figure.where(kind: kind): set figure.caption(position: position) show figure: it => { let num = numbering(subcapnumbering, n-super, quartosubfloatcounter.get().first() + 1) show figure.caption: it => block({ num.slice(2) // I don't understand why the numbering contains output that it really shouldn't, but this fixes it shrug? [ ] it.body }) quartosubfloatcounter.step() it counter(figure.where(kind: it.kind)).update(n => n - 1) } quartosubfloatcounter.update(0) body } )#label] } } // callout rendering // this is a figure show rule because callouts are crossreferenceable #show figure: it => { if type(it.kind) != str { return it } let kind_match = it.kind.matches(regex("^quarto-callout-(.*)")).at(0, default: none) if kind_match == none { return it } let kind = kind_match.captures.at(0, default: "other") kind = upper(kind.first()) + kind.slice(1) // now we pull apart the callout and reassemble it with the crossref name and counter // when we cleanup pandoc's emitted code to avoid spaces this will have to change let old_callout = it.body.children.at(1).body.children.at(1) let old_title_block = old_callout.body.children.at(0) let children = old_title_block.body.body.children let old_title = if children.len() == 1 { children.at(0) // no icon: title at index 0 } else { children.at(1) // with icon: title at index 1 } // TODO use custom separator if available // Use the figure's counter display which handles chapter-based numbering // (when numbering is a function that includes the heading counter) let callout_num = it.counter.display(it.numbering) let new_title = if empty(old_title) { [#kind #callout_num] } else { [#kind #callout_num: #old_title] } let new_title_block = block_with_new_content( old_title_block, block_with_new_content( old_title_block.body, if children.len() == 1 { new_title // no icon: just the title } else { children.at(0) + new_title // with icon: preserve icon block + new title })) align(left, block_with_new_content(old_callout, block(below: 0pt, new_title_block) + old_callout.body.children.at(1))) } // 2023-10-09: #fa-icon("fa-info") is not working, so we'll eval "#fa-info()" instead #let callout(body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black, body_background_color: white) = { block( breakable: false, fill: background_color, stroke: (paint: icon_color, thickness: 0.5pt, cap: "round"), width: 100%, radius: 2pt, block( inset: 1pt, width: 100%, below: 0pt, block( fill: background_color, width: 100%, inset: 8pt)[#if icon != none [#text(icon_color, weight: 900)[#icon] ]#title]) + if(body != []){ block( inset: 1pt, width: 100%, block(fill: body_background_color, width: 100%, inset: 8pt, body)) } ) } // arxivai — Quarto Typst template partial. // Replaces Quarto's default `article()` with the arxivai preprint layout. // Both packages are importable from raw ```{=typst}``` blocks in the body: // #arxivai.operation-box(...) #arxivai.app-figure(...) #lettreai.badge(...) #import "lettreai.typ" as lettreai #import "arxivai.typ" as arxivai // --------------------------------------------------------------------------- // Re-theme Quarto's automatic callouts (same contract as lettreai: block // nesting kept structurally identical to Quarto's default so the crossref // figure show rule in definitions.typ keeps working). // --------------------------------------------------------------------------- #let callout( body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black, body_background_color: white, ) = { let accent = if icon_color == black { lettreai.colors.oxford-blue } else { icon_color } block( breakable: false, fill: lettreai.tint(accent, 0.94), stroke: (left: 3pt + accent, rest: 0.5pt + lettreai.tint(accent, 0.5)), width: 100%, radius: 3pt, block( inset: 1pt, width: 100%, below: 0pt, block( fill: lettreai.tint(accent, 0.84), width: 100%, inset: 8pt, )[#if icon != none [#text(accent, weight: 900)[#icon] ]#text(fill: lettreai.shade(accent, 0.25), weight: 700)[#title]]) + if (body != []) { block( inset: 1pt, width: 100%, block(fill: body_background_color, width: 100%, inset: 8pt, body)) } ) } // --------------------------------------------------------------------------- // arXiv preprint article — same signature contract as Quarto's default, // extended with keywords and running-title. // --------------------------------------------------------------------------- #let article( title: none, subtitle: none, authors: none, keywords: (), running-title: none, book: false, date: none, abstract-title: none, abstract: none, thanks: none, cols: 1, lang: "en", region: "US", font: none, fontsize: 10.5pt, title-size: 1.7em, subtitle-size: 1.15em, heading-family: none, heading-weight: "bold", heading-style: "normal", heading-color: black, heading-line-height: 0.65em, mathfont: none, codefont: none, linestretch: 1, sectionnumbering: none, linkcolor: none, citecolor: none, filecolor: none, toc: false, toc_title: none, toc_depth: none, toc_indent: 1.5em, doc, ) = { // PDF metadata. set document(title: title) set document( author: authors.map(a => lettreai.to-string(a.name)).join(", ", last: " & "), ) if authors != none and authors != () set document( keywords: keywords.map(k => lettreai.to-string(k)), ) if keywords != none and keywords != () set par(justify: true, leading: linestretch * 0.65em) set text(lang: lang, region: region, size: fontsize) show math.equation: set text(font: mathfont) if mathfont != none set heading(numbering: sectionnumbering) // Running header + footer; book mode gets chapter-aware heads and the // O'Reilly outer-margin geometry. let rt = if running-title != none { running-title } else if title != none { title } else { [] } set page( header: if book { arxivai.book-header() } else { arxivai.arxiv-header(rt) }, footer: if book { arxivai.book-footer() } else { arxivai.arxiv-footer() }, ) set page(margin: arxivai.book-margin) if book // Resolve fonts: explicit Quarto fonts win, else theme defaults. let resolved-font = if font != none { font } else { lettreai.body-font } let resolved-code = if codefont != none { codefont } else { lettreai.mono-font } let resolved-heading-color = if heading-color != black { heading-color } else { lettreai.colors.oxford-blue } let resolved-link = if linkcolor != none { rgb(lettreai.to-string(linkcolor)) } else { lettreai.colors.oxford-blue } // Paint everything with the shared theme, then the figure rules. show: lettreai.theme-rules.with( fonts: resolved-font, code-font: resolved-code, heading-color: resolved-heading-color, link-color: resolved-link, ) show: arxivai.arxiv-figure-rules // Book chapter rule is the innermost show, so it transforms level-1 // headings FIRST (inner rules apply first), replacing them with chapter // openers before the theme's h1 rule could wrap them in a container. // Synthetic non-outlined headings (e.g. the outline's own title) are // excluded inside book-heading-rules. show: doc2 => if book { arxivai.book-heading-rules(doc2) } else { doc2 } show ref: set text(fill: rgb(lettreai.to-string(citecolor))) if citecolor != none // Preprint front matter. arxivai.arxiv-title-block( title: title, subtitle: subtitle, authors: authors, date: date, ) if abstract != none { arxivai.arxiv-abstract( abstract, abstract-title: if abstract-title == none { [Abstract] } else { abstract-title }, ) } arxivai.arxiv-keywords(keywords) // Book mode: the front matter is a title page of its own. if book { pagebreak(weak: true) } // Optional table of contents (its own page in book mode). if toc { block(above: 0.5em, below: 2em, lettreai.lettreai-outline( title: if toc_title == none { auto } else { toc_title }, depth: toc_depth, indent: toc_indent, )) if book { pagebreak(weak: true) } } if cols > 1 { columns(cols, doc) } else { doc } } #let brand-color = (:) #let brand-color-background = (:) #let brand-logo = (:) #set page( paper: "a4", margin: (x: 2.4cm,y: 2.6cm,), numbering: "1", columns: 1, ) #show: doc => article( title: [Famulus normalisation], subtitle: [One statement for a sourced reading], authors: ( ( name: [Gerd Graßhoff], affiliation: [Philosophisches Institut, Forschungsinstitut des Deutschen Museums], email: [gerd.grasshoff\@hu-berlin.de], orcid: [0000-0002-4450-357X] ), ), abstract: [Expression normalisation is stored as one kind of row: a statement with a source, a scope, and a predicate from a closed registry. This paper reports a first reading of the name index in Euler's correspondence with Goldbach, Opera omnia IVA 4, and a correspondent sample from the Kármán Nachlass. The index occupies 42 OCR pages. Every page that carries a running header places that header 7 page-indexes after the printed number. The parse yields 993 entries, 3,893 printed-page attestations, and 6,338 statements before identity links. Against the 311 eulerOO-IVA identities the register held on 17 October, 130 index heads match one identity by surname and initial, and 27 match more than one and stay unresolved. After the register stage was run for the collection on 19 October, the register holds 3,067 identities for it; the same rule then links 285 heads and leaves 184 unresolved. The Kármán sample of 50 correspondent folders has not been reviewed, so no precision is stated. The vector store was removed on 20 October; BM25 replaced it as the recall path for paraphrase, at a measured loss @grasshoff2026famulusdatalayer. ], abstract-title: "Abstract", font: ("Source Sans 3",), sectionnumbering: "1.1", toc_title: [Table of contents], toc_depth: 3, doc, ) = A statement is the row A mention, a life date, an office, an identity, and an expert answer are the same kind of row. The row names a subject, a predicate, an object, and a source. The predicate is taken from a registry. A claim that the registry does not name is refused. Two writers who store the same claim from the same source obtain the same identifier, so a merge is a union. The time of a statement is four day-number bounds. An Allen relation may stand without a calendar date. Path consistency may tighten a bound, and it records which pair forced the tightening. A bound that crosses itself is reported. It is not deleted. = The name index of IVA 4 The volume on disk is `eulerOO-IVA/978-3-0348-0892-7`. Its bibliography file names the work #emph[Correspondence of Leonhard Euler with Christian Goldbach], edited by Franz Lemmermeyer and Martin Mattmüller, in #emph[Leonhardi Euleri Opera omnia], series IVA, volume 4, and dates the online edition 2016 @euler-goldbach. The name index is the OCR of page indexes 1214 through 1255, 42 pages. The running header gives the printed page. On page index 1230 the header reads "Name Index 1223". On page index 1223 it reads "1216 Name Index". The difference is 7 on every page in the range that carries such a header. A printed page (n) is therefore stored as page index (n + 7). Roman numerals in the index are kept as printed tokens and are not given a page index. Each OCR text node that is an entry is stored as its own quote, so the 993 entries are 993 quotes. The head is read by a pattern: the name, a life span when the parentheses contain years, a "see" pointer, and the page list after the colon. A year that the pattern does not recognise is not filled in. The run of 17 October 2026 and its repetition on 20 October produced the counts in #ref(, supplement: [Table]). #figure([ #table( columns: 3, align: (left,right,right,), table.header([], [17 October], [20 October],), table.hline(), [Pages], [42], [42], [Header offset], [7], [7], [Entries], [993], [993], [Attestations mapped to a page index], [3,893], [3,893], [Entities], [1,035], [1,035], [Register identities for eulerOO-IVA], [311], [3,067], [Heads linked to one identity], [130], [285], [Heads matching more than one identity], [27], [184], [Statements], [6,467], [6,622], ) ], caption: figure.caption( position: top, [ The name index of IVA 4 read against the register of each date. ]), kind: "quarto-float-tbl", supplement: "Table", ) The parse itself did not change: it yields 6,338 statements, and the identity links add the rest. An index head is linked when its surname is the last token of exactly one register identity, or when the given-name initial narrows several forms to one identity. On 17 October the global register held 386 name rows and 311 distinct identities for eulerOO-IVA. That linked 130 heads. Twenty-seven heads, among them the several Bernoullis and the several Eulers, matched more than one identity. They are listed and not collapsed. On 19 October the register stage was run for eulerOO-IVA; 2,854 of the collection's identities date from that run. The copy read on 20 October holds 3,236 name rows and 3,067 identities for the collection: 2,114 persons, 529 places, and 424 institutions. The rule reads all of them, not only persons. Against that register it links 285 heads and leaves 184 with more than one identity. A larger register makes more surnames ambiguous. No precision is stated for either run, because no link has been reviewed. One entry, read from page index 1223, is Johann Jacob van Delen, with the years 1743 and 1786 in parentheses and the printed pages 570, 574, 1129, and 1132. Those become page indexes 577, 581, 1136, and 1139. The biography clause is the OCR node itself. = A Kármán sample `letters_resolved` in the Kármán Nachlass holds 15,976 rows in 1,066 folders. Correspondent folders are those whose identifier begins with a numeric pair, continues with a name, and contains a year, excluding calculation files, indexes, covers, inventories, miscellanea, and bibliographies. There are 401 such folders. The sample is the 25 folders with the most letters and 25 others drawn with seed 17. It contains 1,290 letters, of which 407 had an empty `date_when` on 17 October and 409 on 20 October. The largest folder in the sample is `11-40_guggenheim_harry_f_1926_1958_i`, with 64 letters. The owner has not reviewed the sample. No precision is stated. = What is not claimed The statement model is not yet the store of the other Famulus layers. The global register decides identities in its own database. The expression normaliser of famulus-proof writes its footnotes into the sentence record (`sr_expr`, `sr_expr_grund`, and the keys of `sr_attr`), which now lives in the collection's famulus file `_famulus.duckdb` @grasshoff2026famulusdatalayer. Dossier facts are XML files in the collection's `dossier` folder with a projection (`do_*`) in the same famulus file. Only the explication pilot of 17 October wrote its resolved referents as statements of this model, 504 `refers_to` rows. A merge of this package into famulus-proof, so that one store carries footnotes, explications, and statements, is planned and not done. The vector store used by the query service was removed on 20 October by owner decision; a BM25 index in the collection's corpus file (over sentence text and lemmatised sentence, with a character 4-gram index for German and French fused by reciprocal rank fusion) is now the recall path for paraphrase. On 200 paraphrase probes it finds the source sentence in the top 50 for 0.830 of them and in the top 10 for 0.725; the bge-m3 vector store did for 0.895 and 0.890 @grasshoff2026famulusdatalayer. That loss is not recovered by the statement routes, and the golden queries were not compared with them. The calendar service answers a Julian day number, the weekday of that number, and the Gregorian Easter date. Easter is accepted when the date falls on a Sunday and lies between 22 March and 25 April. Territorial adoption tables, feast days beyond Easter, and the historical hour systems are not in this build. The astronomical service on port 5142 is a separate expert. A sentence that names a longitude points at a calculation tool, or it says that the stated position cannot be retrieved. #bibliography(("famulus-normalisation-bibliography.bib"))