Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/demo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Web demo

# The equation editor on WebAssembly (samples/SharpEquations.Avalonia.Browser.Demo): built on every pull request,
# The equation editor on WebAssembly (samples/SharpEquations.Avalonia.Browser.Demo), and the math tools under math/
# (samples/SharpEquations.Avalonia.Browser.MathTools.Demo): built on every pull request,
# published to GitHub Pages from main, or from any branch when the workflow is run by hand (Settings → Pages → Source:
# GitHub Actions; a branch other than main must be allowed in Settings → Environments → github-pages).
on:
Expand All @@ -22,6 +23,7 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: 'true'
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 'true'
PROJECT: samples/SharpEquations.Avalonia.Browser.Demo/SharpEquations.Avalonia.Browser.Demo.csproj
MATH_TOOLS_PROJECT: samples/SharpEquations.Avalonia.Browser.MathTools.Demo/SharpEquations.Avalonia.Browser.MathTools.Demo.csproj

jobs:
build:
Expand All @@ -43,11 +45,16 @@ jobs:
- name: Publish
run: dotnet publish "$PROJECT" -c Release -o publish

# GitHub Pages serves the site from a folder: no Jekyll, and the index for any path that is not a file
- name: Publish the math tools
run: dotnet publish "$MATH_TOOLS_PROJECT" -c Release -o publish-math-tools

# GitHub Pages serves the site from a folder: no Jekyll, and the index for any path that is not a file; the math
# tools are a site of their own in math/ (their pages are relative to it)
- name: Prepare the site
run: |
touch publish/wwwroot/.nojekyll
cp publish/wwwroot/index.html publish/wwwroot/404.html
cp -r publish-math-tools/wwwroot publish/wwwroot/math

- uses: actions/upload-pages-artifact@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ generated parser, on netstandard2.0): keep it so, and keep containers and other
| `src/SharpEquations.Avalonia/` | The controls for Avalonia: `MathEditBox` (the editor), `MathView`; embeds Libertinus Math |
| `samples/SharpEquations.Avalonia.Demo/` | A window with the editor (`DemoView`, shared with the web demo) |
| `samples/SharpEquations.Avalonia.Browser.Demo/` | The same demo on WebAssembly (needs the `wasm-tools` workload, so not in the solution; `.github/workflows/demo.yml` publishes it to GitHub Pages) |
| `samples/SharpEquations.Avalonia.MathTools.Demo/` | A second app, math tools around the editor (`MathToolsView`, a page each: the scientific keypad) |
| `samples/SharpEquations.Avalonia.Browser.MathTools.Demo/` | The math tools on WebAssembly (as the web demo, not in the solution build; `demo.yml` publishes them under `math/`) |
| `benchmarks/SharpEquations.Benchmarks/` | BenchmarkDotNet benchmarks of reading, writing and converting the corpora |
| `tests/SharpEquations.Tests/` | TUnit tests, in folders by subject (PublicApi, MathML, Omml, Mtef, UnicodeMath, LaTeX, Property, Regression) |
| `tests/SharpEquations.Avalonia.Tests/` | TUnit tests of the editor (Editing, Rendering, and the controls in a headless Avalonia session with Skia) |
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ three projects, so that the core keeps no dependency: `SharpEquations.Editing` (
[docs/EDITOR.md](docs/EDITOR.md); `samples/SharpEquations.Avalonia.Demo` is a window with the editor and its MathML,
UnicodeMath and LaTeX, and `samples/SharpEquations.Avalonia.Browser.Demo` the same demo in the browser, on WebAssembly
(published to GitHub Pages from `main`; to run it locally: `dotnet workload install wasm-tools`, then
`dotnet run --project samples/SharpEquations.Avalonia.Browser.Demo`).
`dotnet run --project samples/SharpEquations.Avalonia.Browser.Demo`). `samples/SharpEquations.Avalonia.MathTools.Demo` is a
second app around the editor, a set of math tools (a scientific keypad that types into the editor), with
`samples/SharpEquations.Avalonia.Browser.MathTools.Demo` in the browser (published under `math/` of the same site).

## Architecture

Expand Down
4 changes: 4 additions & 0 deletions SharpEquations.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
<Project Path="samples/SharpEquations.Avalonia.Browser.Demo/SharpEquations.Avalonia.Browser.Demo.csproj">
<Build Project="false" />
</Project>
<Project Path="samples/SharpEquations.Avalonia.Browser.MathTools.Demo/SharpEquations.Avalonia.Browser.MathTools.Demo.csproj">
<Build Project="false" />
</Project>
<Project Path="samples/SharpEquations.Avalonia.Demo/SharpEquations.Avalonia.Demo.csproj" />
<Project Path="samples/SharpEquations.Avalonia.MathTools.Demo/SharpEquations.Avalonia.MathTools.Demo.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path="AGENTS.md" />
Expand Down
36 changes: 36 additions & 0 deletions samples/SharpEquations.Avalonia.Browser.MathTools.Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace SharpEquations.Avalonia.MathTools
{
using System.Runtime.Versioning;
using System.Threading.Tasks;
using global::Avalonia;
using global::Avalonia.Browser;
using global::Avalonia.Controls.ApplicationLifetimes;
using global::Avalonia.Media;

[SupportedOSPlatform("browser")]
public static class Program
{
public static Task Main() => BuildAvaloniaApp().StartBrowserAppAsync("out");

// A page has no fonts of its own: Inter for the text, and the embedded Libertinus Math for the symbols it lacks.
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>()
.WithInterFont()
.With(new FontManagerOptions
{
DefaultFamilyName = "fonts:Inter#Inter",
FontFallbacks = new[] { new FontFallback { FontFamily = new FontFamily("fonts:SharpEquations#Libertinus Math") } },
});
}

public sealed class App : Application
{
public override void Initialize() => MathToolsTheme.Apply(this);

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is ISingleViewApplicationLifetime view)
view.MainView = new MathToolsView();
base.OnFrameworkInitializationCompleted();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">

<!-- The math tools in the browser, with Avalonia on WebAssembly: the view of the desktop app in a page. Needs the
wasm-tools workload (dotnet workload install wasm-tools); dotnet publish -c Release writes the site to
bin/Release/net10.0-browser/publish/wwwroot. -->
<PropertyGroup>
<TargetFramework>net10.0-browser</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<RootNamespace>SharpEquations.Avalonia.MathTools</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RunAOTCompilation Condition="'$(Configuration)' == 'Release'">true</RunAOTCompilation>
<WasmBuildNative>true</WasmBuildNative>
<WasmEnableThreads>false</WasmEnableThreads>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
<!-- every file of the desktop app but its window and its entry point -->
<Compile Include="..\SharpEquations.Avalonia.MathTools.Demo\*.cs" Exclude="..\SharpEquations.Avalonia.MathTools.Demo\Program.cs;..\SharpEquations.Avalonia.MathTools.Demo\MainWindow.cs" LinkBase="MathTools" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia.Browser" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<PackageReference Include="Avalonia.Themes.Fluent" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\SharpEquations.Avalonia\SharpEquations.Avalonia.csproj" />
</ItemGroup>

</Project>
123 changes: 123 additions & 0 deletions samples/SharpEquations.Avalonia.Browser.MathTools.Demo/wwwroot/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
:root {
--page: #f3f4fa;
--ink: #1f2333;
--muted: #6b7280;
--track: rgba(15, 118, 110, 0.15);
}

@media (prefers-color-scheme: dark) {
:root {
--page: #0d0f16;
--ink: #e7e9f2;
--muted: #9aa3b5;
--track: rgba(94, 234, 212, 0.18);
}
}

html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: var(--page);
color: var(--ink);
font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#out {
position: fixed;
inset: 0;
}

#splash {
position: fixed;
inset: 0;
display: grid;
place-items: center;
background:
radial-gradient(60rem 40rem at 10% -10%, rgba(20, 184, 166, 0.35), transparent 60%),
radial-gradient(50rem 40rem at 110% 110%, rgba(37, 99, 235, 0.28), transparent 60%),
var(--page);
transition: opacity 0.45s ease, visibility 0.45s;
z-index: 10;
}

#splash.done {
opacity: 0;
visibility: hidden;
}

#splash .card {
text-align: center;
padding: 2.5rem 3rem;
max-width: 26rem;
}

#splash .formula {
display: flex;
justify-content: center;
gap: 0.9rem;
font-size: 2.6rem;
font-family: "Cambria Math", "STIX Two Math", "Latin Modern Math", serif;
background: linear-gradient(120deg, #0f766e, #0891b2 55%, #2563eb);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}

#splash .formula span {
display: inline-block;
animation: float 1.8s ease-in-out infinite;
}

#splash .formula span:nth-child(2) { animation-delay: 0.15s; }
#splash .formula span:nth-child(3) { animation-delay: 0.3s; }
#splash .formula span:nth-child(4) { animation-delay: 0.45s; }

@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-0.4rem); }
}

#splash h1 {
margin: 0.8rem 0 0.3rem;
font-size: 1.7rem;
font-weight: 700;
letter-spacing: -0.01em;
}

#splash p {
margin: 0 0 1.4rem;
color: var(--muted);
}

#splash .bar {
height: 6px;
border-radius: 999px;
background: var(--track);
overflow: hidden;
}

#splash #progress {
width: 4%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #0f766e, #0891b2, #2563eb);
transition: width 0.25s ease;
}

#splash small {
display: block;
margin-top: 1rem;
color: var(--muted);
font-size: 0.75rem;
letter-spacing: 0.02em;
}

#splash.failed .bar {
display: none;
}

@media (prefers-reduced-motion: reduce) {
#splash .formula span { animation: none; }
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SharpEquations Math Tools</title>
<meta name="description" content="A scientific keypad, formulas, units and triangles around a real-time equation editor in .NET and Avalonia, running on WebAssembly." />
<base href="./" />
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
<link rel="modulepreload" href="./main.js" />
<link rel="modulepreload" href="./_framework/dotnet.js" />
<link rel="stylesheet" href="./app.css" />
</head>
<body>
<div id="out"></div>
<div id="splash" aria-live="polite">
<div class="card">
<div class="formula">
<span>sin</span><span>√</span><span>π</span><span>△</span>
</div>
<h1>SharpEquations Math Tools</h1>
<p id="message">Loading the math tools…</p>
<div class="bar"><div id="progress"></div></div>
<small>.NET on WebAssembly · Avalonia · OpenType MATH</small>
</div>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { dotnet } from './_framework/dotnet.js';

const splash = document.getElementById('splash');
const progress = document.getElementById('progress');

try {
const runtime = await dotnet
.withDiagnosticTracing(false)
.withApplicationArgumentsFromQuery()
.withResourceLoader((type, name, url) => url)
.withModuleConfig({
onDownloadResourceProgress: (loaded, total) => {
if (total > 0) progress.style.width = `${Math.round(100 * loaded / total)}%`;
},
})
.create();
const config = runtime.getConfig();
const started = runtime.runMain(config.mainAssemblyName, [globalThis.location.href]);
const hide = () => splash.classList.add('done');
new MutationObserver((_, observer) => {
if (document.querySelector('#out canvas')) { hide(); observer.disconnect(); }
}).observe(document.getElementById('out'), { childList: true, subtree: true });
await started;
} catch (error) {
splash.classList.add('failed');
document.getElementById('message').textContent = `The math tools could not start: ${error}`;
throw error;
}
Loading
Loading