Repository navigation
Equation editor for Avalonia: MathEditBox, MathView, and their packages - #20
Open
simonebortolin wants to merge 74 commits into
Open
simonebortolin wants to merge 74 commits into
simonebortolin wants to merge 74 commits into
Conversation
tests/data/latex holds the 327 conversions and 10 macros of latex2mathml
(LaTeX with the MathML it writes), tests/data/mathml the XSpec scenarios
and examples of mml2tex (MathML with the LaTeX it writes). The MathML
writes LaTeX that reads back stably, the LaTeX reads as latex2mathml
reads it once the conventions of its MathML are made the same, and the
macros expand to their equivalent; what does not yet is listed, with
why, and a case that starts to pass must leave the list.
Writer: an empty identifier is {}, not \operatorname{}; the marks over a
base are \mathring, \overleftarrow, \overleftrightarrow, \overline; Ⓢ is
\circledS; a capital Greek letter is \Gamma upright and \mathit{\Gamma}
italic, not \text{Γ} and \Gamma.
Reader: two \overset in a row, each grouped whole; \mid a bar that
EscapeOddBars escapes without its pair and that is the bar of a ket
before ⟩; \mathring, \overleftarrow, \overleftrightarrow, \circledS; a
phantom with scripts braced; \_ held before a command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…version] Pin the OMML written for math alphanumerics (plain letters with m:scr/m:sty) and emoji, how OMML with astral characters reads, and the round trips. The tests named ...CountingUtf16Units depend on mml2omml.xsl counting UTF-16 units under XslCompiledTransform: 🍕 counts as two characters and is written upright (m:sty p) in a run of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2mathml tests
Reader:
- \limits puts the scripts of an operator under and over it (┬ and ┴),
\nolimits at its side, and in a display equation the scripts of a
summation (\sum, \prod, \bigcup...) go under and over it, as TeX puts
them; an integral keeps them at its side. The reader is told whether
the equation is display (ToUnicodeMath, from FromLaTeX).
- the blanks around a script are left out: x ^ { 2 }.
- \begin{array} { l }: the column specification after a blank; matrix*
with its optional alignment; \hline, \hdashline, \hfil left out.
- the symbols of amsmath, amssymb and plain TeX that UnicodeMath has no
control word for: \lt, \gt, \dotsb and the other \dots, \S, \omicron,
\gggtr, \nsubseteqq...; \emph as text.
Writer: \limits or \nolimits after a large operator whose scripts are
not where TeX puts them (under and over an inline summation or an
integral, at the side of a display summation).
latex2mathml corpus: the conventions of its MathML made the same as ours
(~ and ̃, munder with ― and menclose, empty matrices, mathvariant as math
alphanumerics, mi and mo for symbols, intent=":text", largeop, mstyle);
nine snapshots corrected where its MathML is not what the LaTeX means
(the ~ of text, ( - 25 ) ^ { 2 }, \omicron, \lim in display); and what
the reader still reads otherwise in four groups: commands Word's LaTeX
does not have, LaTeX not read yet, the same equation held otherwise,
what is no LaTeX.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
omml2mml.xsl writes no mathvariant on numbers, so a bold one of OMML
(m:sty b, "1") was read as a plain <mn>1</mn>: 𝟏 of UnicodeMath and
\mathbf{1} of LaTeX lost their bold through OMML. The digits of a bold,
double-struck, sans-serif or monospace run now go through the stylesheet as
their mathematical alphanumerics, each an identifier, as UnicodeMath and
LaTeX read them, so the round trip gives the same equation. Monospace is
read but still not written: mml2omml.xsl writes no m:scr for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…8c6b [no-new-version] OMML: tests of astral characters, styled digits keep their style
…not read
- extensible arrows with an optional argument and an unbraced label
(\xrightarrow[y] x, \xLeftarrow, \xleftrightarrow, \xhookleftarrow),
the arrows under a base (\underleftarrow), unbraced arguments of
stacks, \boxed, \dbinom, \tbinom and \cfrac (\underset ab, \dbinom a b)
- \underset{a}{\overset{b}{X}} as one munderover, as the writer writes it
- \substack as a matrix, \sideset as the scripts on the phantom of the
operator, \genfrac with escaped delimiters (\genfrac\{]...)
- \pod, \mathstrut, \not over a symbol or a group, \lvert and \rvert as
bars, \color{}, \mathscr, \idotsint, \Box, \operatorname* and
\operatornamewithlimits
- a script or a prime without a base (^3, 'x) on an empty one; a comma
before a digit parts two numbers (a_{1,1}, a_{m,1})
- an argument can be a command with its arguments or an escaped
character (\not\operatorname{R}, \genfrac\{)
latex2mathml corpus: empty placeholders, zero lengths, the thickness of a
bar, the moves of mpadded and the digits of a math alphabet (one
identifier, as OMML reads them since #6) made the same; 103 cases left,
18 of them LaTeX not read yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t| _0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l Greek written as \Delta
{\rm ...}, {\bf ...}, {\it ...}, {\cal ...}, {\sf ...} and {\tt ...} style the
rest of their group, as \mathrm{...} and the others. An upright capital Greek
letter is written \Delta rather than \text{Δ}, which read back as text: the
capitals of LaTeX are upright.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… as it, not \overline (¯)
\bar{x} reads as menclose notation="top", \overline{x} as an mover with ¯: the
writer wrote both as \overline, so that \bar{x} read back as another equation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…om it, and takes scripts
The script of ┴ took what came after the stack (a\overset{n\to\infty}{\longrightarrow}b
read b into it): a space ends it, also after \xrightarrow. \overset{a}{b}^2, which
was not valid UnicodeMath, is grouped for its script.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…which would be its numerator lim_(ℎ→0) 𝑓(𝑥)/ℎ and ⟶┴(𝑛→∞) 1/2 read back as fractions of all that came before: the function apply sign does not end a numerator, and the space after a script ends the script, not the numerator. The fraction is grouped there: lim_(ℎ→0) 〖𝑓(𝑥)/ℎ〗, ⟶┴(𝑛→∞) 〖1/2〗. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LaTeX: the test cases of latex2mathml and mml2tex, and what they found
…latex-webc-fixes
The conflicts take what main does, which covers what this branch did there:
\lt and \gt among the symbols of main, the blanks around a script removed
before and after it, \overset grouped whole (which keeps it apart from what
follows, so EndScript goes), upright capital Greek written \Gamma. Kept: \degree
for °, and calligraphic-mode, which {\cal ...} now reads, out of the known
failures of the latex2mathml corpus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Content MathML (apply, ci, cn...) outside the annotations is rewritten as
presentation MathML by David Carlisle's ctop.xsl (MIT), keeping the
presentation MathML around it. White space between elements and around
the text of ci/cn is removed first, and the limits ctop leaves empty
after. ctop.xsl is fixed for nested mi in bvar, x^{} in partialdiff
without a degree, an unprefixed mo, a bare log and ∫ as mi. The 40
equations of ctop's test page are the corpus of the new tests.
From the table of entities.xsl (XSLT MathML Library): about 70 amssymb
symbols written as commands, 15 commands the LaTeX reader did not know
(\square, \surd, \triangleq, \S...), and □ and √ written as \square and
\surd in standard LaTeX instead of \□ and \√.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…□ and √, upright names with digits
- UnicodeMath writer: a bar that is the base of a script is written ├| (sin├|_ℤ), a slash
with nothing before it as ⓡ(/&U+002F), a bar without its pair inside brackets as
ⓡ(|&U+007C) ([𝑎|𝑏] read as [𝑎 and the fence |𝑏]), and no ┤ is added before a fenced
row at the end of the equation
- UnicodeMath grammar: a name marked as a function without an operand (NaN,) is one
name, so \operatorname{NaN}, reads back as NaN and stays stable
- Word's LaTeX: an identifier that is a UnicodeMath structure operator (□, √) is escaped
- OMML reader: an upright name with digits (sharet1) is one identifier
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LaTeX and UnicodeMath fixes from the math.webc.site demos
- Standard LaTeX writer: □ as \square, √ as {\surd} (the symbol, not a root); the reader
reads \square and \Box as □
- UnicodeMath writer: an identifier or an operator that builds a structure in UnicodeMath
(□, √, ⒜...) is written as a remark, ⓡ(□&U+25A1), which reads back as that operator
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The structure operators of standard LaTeX take main's command lookup (\circledS) besides
\square and {\surd}; \operatorname{lg} now reads back, so it leaves the known failures of
the latex2mathml corpus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… \underbrace
\mathop of one symbol is a group, to which \limits attaches its scripts
(munderover(x, 0, 1)); \overbrace and \underbrace with both scripts are, as
for TeX, operators with limits: \underset{x}{\overset{a}{\overbrace{3}}}.
The latex2mathml cases issue-125-4-limits, overbrace-c and underbrace-c pass.
[no-new-version]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…does
\begin{name}{arguments}...\end{name} becomes the begin code with its #1...
(the first argument optional with a default), the body and the end code,
before the other passes; the definitions go to the macros, where later
conversions find them. ## in a macro body is # in math mode, which TeX
rejects: newcommand-escaped-hash is no LaTeX.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter-fixes Writer and reader fixes found with the Content MathML corpus
…acdf3 # Conflicts: # tests/SharpEquations.Tests/LaTeX/Latex2MathMLCorpusTests.cs
…sts to any branch Stacked pull requests (based on another PR's branch rather than main) ran no workflow at all, since every pull_request trigger was filtered to main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s out of NotInWordLaTeX [no-new-version] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The groups of NotInWordLaTeX taken from main, without the seven cases the reader now reads (\tt, \Bbb, \mathop, the arrows of mathtools), and \Box renamed once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tests, CodeQL and the cleanup of the pre-release run for a pull request whatever its base, so that a PR stacked on another one is tested too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tex-symbols-readback-e290e7 LaTeX reader reads every command the writer writes
…F-8 bytes Word writes the "MathML" clipboard format as UTF-8 bytes; read as a string of Windows, as UTF-16, each pair of bytes became a CJK character, and the MathML that did not parse was inserted as text, before the plain text of the clipboard. The format is now read and written as bytes (MathClipboard: UTF-8, or UTF-16 with its byte order mark), and MathML or OMML that does not parse inserts nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q8QQiaNyPsgRSRubdbT58H
…chem mhchem: \ce and \pu in the LaTeX reader
…reads
Since main reads a script without a base as one on an empty base ({^3H} is
{}^3 H, as in TeX), the prescripts mhchem wrote at the start of a group
were read as scripts of an empty base. The reader reads \prescript of
mathtools as UnicodeMath prescripts, and mhchem writes them with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyVio98rJkziNnwWW9T1Mn
…ex-limits-dacdf3
LaTeX reader: limits of \mathop{x} and both scripts of \overbrace/\underbrace
…tex-macros-bd8629 LaTeX reader: \newenvironment and \renewenvironment
Content MathML read through ctop.xsl; amssymb symbols in LaTeX
…nd-space-42f4d4 Unknown LaTeX control words end at their letters
…tex-bars-spaces-89b298 LaTeX reader: space commands, \ outside environments, \middle beside empty sides
samples/SharpEquations.Avalonia.Browser.Demo runs the demo in the browser with Avalonia.Browser: Inter for the text and the embedded Libertinus Math as its fallback, System.Private.Xml kept whole for the OMML stylesheets, a splash with the download progress. The desktop window and the page share DemoView, now redesigned: a header, cards, a ribbon of structures drawn as formulas, symbols by group, a gallery of examples rendered with MathView, the outputs with a copy button, the light and the dark theme, and a layout for narrow screens. The project needs the wasm-tools workload, so it is not in the solution: .github/workflows/demo.yml builds it and publishes it to GitHub Pages from main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
The default math font is Libertinus Math everywhere but Windows, so "Default" and "Libertinus Math" were the same font, and Cambria Math was offered where it is not installed: the change of font showed nothing. The demo embeds STIX Two Math (SIL Open Font License), names the default font, offers Cambria Math only where it is a math font, and applies the font to the gallery too. DemoTests.FontChangesTheLayout checks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
…efault font to another and back Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
# Conflicts: # NOTICE
… new structures of LaTeX in the commands After the rebase on main, the LaTeX reader reads the arguments of a command without braces (\underset ab, \xrightarrow[y] x, \boxed \Box, \not a): the editor no longer builds up at a space while a command still awaits its arguments, counted from EquationCommands. The commands list the structures the reader now reads: the extensible arrows of amsmath and mathtools, the arrows under a base, \dddot, \ddddot, \mathscr, \not, \substack, \prescript and \ce, \pu of mhchem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVioB6ZwExLPtzuBzNTgX
…to claude/focused-clarke-f4k4a9
…with, by name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
…to claude/focused-clarke-f4k4a9
…e web demo ctop.xsl, which came from main after the branch, is compiled by the generator too (CtopStylesheet). The stylesheets now count code points as XPath does, not UTF-16 units as XslCompiledTransform did: an emoji (🍕) is an identifier of one letter, written italic in OMML in one run with its neighbours, and goes through OMML and back as the same equation; OmmlAstralTests pins that now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
…h the MATH table Cambria and Cambria Math share their glyphs in cambria.ttc, so the number of glyphs matched Cambria, the first face, which has no MATH table: Cambria Math was never a math font, the default font of Windows was Libertinus Math, and choosing Cambria Math changed nothing. Of the faces with the number of glyphs, FontFile now reads the one with a MATH table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
…ound for XslCompiledTransform With the stylesheets compiled to C#, nothing generates code at run time: the release build of the web demo is compiled to WebAssembly ahead of time, and is trimmed with no warning, without keeping System.Private.Xml whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
…hen run by hand Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyVio7wKz4nEifoqJzu9wA
simonebortolin
had a problem deploying
to
github-pages
November 9, 2026 23:42 — with
GitHub Actions
Failure
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A real-time equation editor for Avalonia in the manner of the
RichEditBoxof WinUI in math mode (math only): UnicodeMath or LaTeX typed at the caret builds up into the equation when a space or an operator ends an argument, and the equation is read and written as MathML (GetMath/SetMath) or as anEquation. The design is indocs/EDITOR.md.Projects
SharpEquations.EditingMathNodetree with runs of linear text, caret, selection, undo (a bounded stack of immutable trees), build up and build downSharpEquations.Renderingglyf/CFF outlines,sstyvariants from GSUB; caret and selection geometrySharpEquations.AvaloniaMathEditBox,MathView, the floating bar (MathQuickBar); Cambria Math on Windows, embedded Libertinus Math (OFL) elsewheresamples/SharpEquations.Avalonia.DemoFeatures
GetSelectedText,CopyTextFormat: MathML pastes into Word,Copyingevent), paste of MathML/UnicodeMath/LaTeX#(1),\tag{1},EquationNumber)&; automatic line breaks of long display equations before the operatorsMathEditBoxAutomationPeer, the equation as UnicodeMathCore
The core's only change is new public API:
EquationCommandandEquationCommands(the control words of UnicodeMath and the commands of LaTeX with their symbols), inPublicAPI.Unshipped.txt, checked against the parsers byPublicApi/EquationCommandsTests. It calls for a minor version:VersionPrefixis still 1.0.5 and the commits say[no-new-version], so the bump is to be made before merging.Release
The CI tests the editor (
tests/SharpEquations.Avalonia.Tests, net10.0, and net8.0 on Linux), and packs and publishes the three packages of the editor with the core, with one version. Their public API is tracked inPublicAPI.*.txt. The package IDsSharpEquations.Editing,.Renderingand.Avaloniaare new on nuget.org: the Trusted Publishing policy ofNUGET_USERhas to cover them.Tests
dotnet test --solution SharpEquations.slnx: 5673 passed. No test was run on Windows or macOS, or with a real window.Generated by Claude Code