diff --git a/.cspell.json b/.cspell.json index 97c15bb81..690c2cc65 100644 --- a/.cspell.json +++ b/.cspell.json @@ -22,7 +22,8 @@ "tikzcd", "Unif", "Vect", - "Xmark" + "Xmark", + "allowbreak" ], "words": [ "abelian", diff --git a/content/Grp_total_explicit_proof.md b/content/Grp_total_explicit_proof.md index 110c8f14f..f8b0633f2 100644 --- a/content/Grp_total_explicit_proof.md +++ b/content/Grp_total_explicit_proof.md @@ -25,7 +25,7 @@ We first need to define a natural transformation $$\eta_T : T \to \Hom({-}, L(T)).$$ For each group $H$ we define the function $\eta_T(H) : T(H) \to \Hom(H, L(T))$ by sending $x \in T(H)$ to the map $$H \to L(T), \quad h \mapsto e(T(\widetilde{h})(x)),$$ -where $\widetilde{h} : \IZ \to H$ denotes the homomorphism mapping $1 \mapsto h$, so that $T(\widetilde{h}) : T(H) \to T(\IZ)$. To see that this defines a group homomorphism from $H$ to $L(T)$, for $h,h' \in H$ we define the homomorphism $[h,h'] : \IZ * \IZ' \to H$ by $[h,h'] \circ \iota_1 = \widetilde{h}$ and $[h,h'] \circ \iota_2 = \widetilde{h'}$. Note that $[h,h'] \circ \mu = \widetilde{hh'}$. Hence, +where $\widetilde{h} : \IZ \to H$ denotes the homomorphism mapping $1 \mapsto h$, so that $T(\widetilde{h}) : T(H) \to T(\IZ)$. To see that this defines a group homomorphism from $H$ to $L(T)$, for $h,h' \in H$ we define the homomorphism $[h,h'] : \IZ * \IZ' \to H$ by $[h,h'] \circ \iota_1 = \widetilde{h}$ and $[h,h'] \circ \iota_2 = \widetilde{h'}$. Note that $[h,h'] \circ \mu = \widetilde{hh'}$. Hence, $$ \begin{align*} diff --git a/src/lib/server/formulas.ts b/src/lib/server/formulas.ts index af5d8d057..afb9ab8db 100644 --- a/src/lib/server/formulas.ts +++ b/src/lib/server/formulas.ts @@ -7,25 +7,196 @@ const MACROS = YAML.parse( fs.readFileSync(path.resolve('database', 'data', 'macros.yaml'), 'utf8') ) as Record -export const MATH_REGEX = /\$\$(.*?)\$\$|\$(.*?)\$/gs +// The third group is the punctuation directly after an inline formula +// (see append_suffix). It does not include ], which would break the syntax +// of Markdown links. +export const MATH_REGEX = /\$\$(.*?)\$\$|\$(.*?)\$([.,;:)-]*)/gs +const HTML_START = '' + last_chunk.markup = + last_chunk.markup.slice(0, -closing_tag.length) + markup + closing_tag + } else { + chunks.push({ markup, is_base: false, is_break_point: false }) + } +} + +/** + * Splits the HTML output of KaTeX, which begins at the given index, into + * its chunks and determines after which of them a line break is preferred. + * Also returns the index where the HTML output ends. + */ +function parse_chunks(html: string, start: number): { chunks: Chunk[]; end: number } { + const chunks: Chunk[] = [] + let depth = 0 + let chunk_start = start + let is_base = false + let last_classes: string[] = [] + let has_allowbreak = false + + // the chunks and their children are spans, so the span tags + // (with their depth) suffice to find them + for (const match of html.slice(start).matchAll(SPAN_TAG_REGEX)) { + const index = start + match.index + + if (match[0] === '') { + // a closing tag at depth 0 closes .katex-html itself + if (depth === 0) return { chunks, end: index } + + depth-- + + // the chunk is complete. It is a break point if its last child + // (ignoring spaces) is a relation or if it contains an explicit + // \allowbreak. + if (depth === 0) { + chunks.push({ + markup: html.slice(chunk_start, index + match[0].length), + is_base, + is_break_point: has_allowbreak || last_classes.includes('mrel') + }) + } + } else { + const classes = match[1]?.split(' ') ?? [] + + // a new chunk begins + if (depth === 0) { + chunk_start = index + is_base = classes.includes('katex-base') + last_classes = [] + has_allowbreak = false + } + // a child of the current chunk + else if (depth === 1) { + if (classes.includes('allowbreak')) has_allowbreak = true + if (!classes.includes('mspace')) last_classes = classes + } + + depth++ + } + } + + throw new Error(`Unexpected HTML output of KaTeX: ${html}`) +} diff --git a/src/lib/server/markdown.ts b/src/lib/server/markdown.ts index ba6457ac3..3d85f63ba 100644 --- a/src/lib/server/markdown.ts +++ b/src/lib/server/markdown.ts @@ -130,19 +130,21 @@ md.block.ruler.before( /** * Replaces the math formulas in a markdown text with placeholders and - * returns a dictionary with the rendered formulas. + * returns a dictionary with the rendered formulas. The placeholder of an + * inline formula also replaces the punctuation directly after it, which + * is rendered with the formula. */ function preprocess_math(content: string) { const formulas: Record = {} const with_placeholders = content.replace( MATH_REGEX, - (_, display_formula, inline_formula) => { + (_, display_formula, inline_formula, suffix) => { const placeholder = `@@MATH-${Object.keys(formulas).length}@@` formulas[placeholder] = display_formula === undefined - ? render_formula(inline_formula, { displayMode: false }) + ? render_formula(inline_formula, { displayMode: false }, suffix) : render_formula(display_formula, { displayMode: true }) return placeholder diff --git a/src/routes/app.css b/src/routes/app.css index 9029b6344..91b2aac60 100644 --- a/src/routes/app.css +++ b/src/routes/app.css @@ -318,12 +318,39 @@ label { } /* cf. https://katex.org/docs/issues */ + +/* allow line breaks in display formulas (KaTeX sets nowrap) */ .katex-display > .katex { white-space: normal; } -.katex-display > .katex > .katex-html > .katex-base { + +/* add space between the lines of a broken display formula */ +.katex-display > .katex > .katex-html > .katex-base, +.katex-display .formula-group > .katex-base { margin: 0.25em 0; } + +/* compensate by reducing the space around display formulas (KaTeX uses 1em) */ .katex-display { margin: 0.5em 0; } + +/* keep formulas and groups of formulas (see formulas.ts) on one line if possible */ +.katex, +.formula-group { + display: inline-block; +} + +/* punctuation after an inline formula (see formulas.ts), in the font of the text */ +.formula-suffix { + font-family: var(--text-font); + font-size: calc(1em / 1.21); /* undo the font size of .katex */ +} + +:is(i, em) .formula-suffix { + font-style: italic; +} + +:is(h1, h2, h3) .formula-suffix { + font-family: var(--mono-font); +}