Denne vejledning viser, hvordan man tilføjer real-time LaTeX matematik rendering til en ASP.NET-app ved hjælp af Aspose.Tex for .NET. Du vil opbygge en lille web API, der accepterer LaTEX input, renderer det til PNG, returnerer billedbyte med den korrekte indholdstype, og caches resultater på disk.
Hvad du vil bygge
En ASP.NET Core app med:
Endpoint
POST /api/latex/png
som accepterer LaTeX og returnerer et PNG-billedeEn simpel HTML-side, hvor brugerne skriver en ekvation og ser en live forudsigelse
Disk caching med en indholdshash og DPI
Grundlæggende inputvalidering og sandboxede arbejdsdialoger
Du kan kopiere koden og køre den som den er.
Forudsætninger
Windows, Linux eller macOS med .NET 6 eller nyere
Visual Studio 2022 eller VS Code* med C# udvidelse
NuGet pakke Aspose.TeX
dotnet add package Aspose.TeX
Aspose.TeX udstillinger TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, og OutputFileSystemDirectory
Du vil bruge disse til at overføre LaTeX til PNG.
Projektlig layout
Skab et ASP.NET Core Web API projekt, derefter tilføje en letvægt service plus en minimal HTML-side.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Service: LaTeX til PNG renderer
Denne tjeneste skriver LaTeX til en midlertidig .tex
filen, kører Aspose.TeX job, og returnerer PNG byter. Det implementerer også en simpel on-disk cache.
// File: Services/LatexRenderer.cs
using System.Security.Cryptography;
using System.Text;
using Aspose.TeX;
namespace AsposeTexDemo.Services;
public sealed class LatexRenderer
{
private readonly string _cacheRoot;
private readonly ILogger<LatexRenderer> _log;
public LatexRenderer(IWebHostEnvironment env, ILogger<LatexRenderer> log)
{
_cacheRoot = Path.Combine(env.ContentRootPath, "tex-cache");
Directory.CreateDirectory(_cacheRoot);
_log = log;
}
// Public entry point. Renders LaTeX to PNG and returns bytes.
public async Task<byte[]> RenderPngAsync(string latexBody, int dpi = 200, CancellationToken ct = default)
{
// Validate and normalize input
var normalized = NormalizeLatex(latexBody);
ValidateLatex(normalized);
// Cache key depends on content and dpi
var key = Hash($"{normalized}\n{dpi}");
var cacheDir = Path.Combine(_cacheRoot, key);
var cachePng = Path.Combine(cacheDir, "out.png");
if (File.Exists(cachePng))
{
_log.LogDebug("Cache hit: {Key}", key);
return await File.ReadAllBytesAsync(cachePng, ct);
}
Directory.CreateDirectory(cacheDir);
// Prepare a minimal document that wraps the math
var texDoc = BuildStandaloneDocument(normalized);
// Write the .tex source into an isolated working folder
var workDir = Path.Combine(cacheDir, "work");
Directory.CreateDirectory(workDir);
var texPath = Path.Combine(workDir, "doc.tex");
await File.WriteAllTextAsync(texPath, texDoc, Encoding.UTF8, ct);
// Configure Aspose.TeX conversion options
var options = TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX);
options.InputWorkingDirectory = new InputFileSystemDirectory(workDir);
options.OutputWorkingDirectory = new OutputFileSystemDirectory(workDir);
var png = new PngSaveOptions
{
// If you want higher fidelity on HiDPI displays, raise this number
Resolution = dpi,
// When false, the ImageDevice buffers PNG bytes in memory so you can capture them without file I/O
// You can also leave the default (true) and read the file from disk. Both modes are shown below.
DeviceWritesImages = false
};
options.SaveOptions = png;
// Run the job; capture PNG bytes from the device
var device = new ImageDevice();
new TeXJob(texPath, device, options).Run();
if (device.Result == null || device.Result.Length == 0)
throw new InvalidOperationException("No PNG output generated by TeX engine.");
var pngBytes = device.Result[0];
// Persist into cache for the next request
await File.WriteAllBytesAsync(cachePng, pngBytes, ct);
// Clean up working files except cachePng if you want to keep cache slim
TryDeleteDirectory(workDir);
return pngBytes;
}
private static void TryDeleteDirectory(string dir)
{
try { if (Directory.Exists(dir)) Directory.Delete(dir, true); }
catch { /* swallow to avoid noisy logs in high traffic */ }
}
// Minimal, safe preamble for math using Object LaTeX
private static string BuildStandaloneDocument(string latexBody)
{
// With standalone class, the output image is tightly cropped around content
return
$@"\documentclass{{standalone}}
\usepackage{{amsmath}}
\usepackage{{amssymb}}
\begin{{document}}
{latexBody}
\end{{document}}";
}
// Allow plain math snippets like x^2 + y^2 = z^2 and also wrapped forms like \[ ... \]
private static string NormalizeLatex(string input)
{
input = input.Trim();
// If user did not wrap math, wrap in display math to get proper spacing
if (!(input.StartsWith(@"\[") && input.EndsWith(@"\]"))
&& !(input.StartsWith(@"$$") && input.EndsWith(@"$$")))
{
return $"\\[{input}\\]";
}
return input;
}
// Very conservative validation to avoid file inclusion or shell escapes
private static void ValidateLatex(string input)
{
// Disallow commands that can touch the filesystem or process environment
string[] blocked = {
@"\write18", @"\input", @"\include", @"\openout", @"\write", @"\read",
@"\usepackage", // preamble is fixed in BuildStandaloneDocument; avoid arbitrary packages
@"\loop", @"\csname", @"\newread", @"\newwrite"
};
foreach (var b in blocked)
{
if (input.Contains(b, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException($"The LaTeX contains a forbidden command: {b}");
}
if (input.Length > 4000)
throw new ArgumentException("Equation too long. Please keep input under 4000 characters.");
}
private static string Hash(string s)
{
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(s));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}
Web API: Minimum ASP.NET Core Endpoint
Dette definerer en JSON-kontrakt, registrerer rendereren og udsætter en POST
Endpoint, der returnerer PNG.
// File: Program.cs
using System.Text.Json.Serialization;
using AsposeTexDemo.Services;
var builder = WebApplication.CreateBuilder(args);
// Optional: serve a simple static page for testing
builder.Services.AddDirectoryBrowser();
// Add the renderer
builder.Services.AddSingleton<LatexRenderer>();
// Configure Kestrel limits for small payloads
builder.WebHost.ConfigureKestrel(opt =>
{
opt.Limits.MaxRequestBodySize = 256 * 1024; // 256 KB per request is plenty for math
});
var app = builder.Build();
// Serve wwwroot for quick manual testing
app.UseDefaultFiles();
app.UseStaticFiles();
// DTOs
public record LatexRequest(
[property: JsonPropertyName("latex")] string Latex,
[property: JsonPropertyName("dpi")] int? Dpi
);
app.MapPost("/api/latex/png", async (LatexRequest req, LatexRenderer renderer, HttpContext ctx, CancellationToken ct) =>
{
if (string.IsNullOrWhiteSpace(req.Latex))
return Results.BadRequest(new { error = "Missing 'latex'." });
int dpi = req.Dpi is > 0 and <= 600 ? req.Dpi.Value : 200;
try
{
var bytes = await renderer.RenderPngAsync(req.Latex, dpi, ct);
ctx.Response.Headers.CacheControl = "public, max-age=31536000, immutable";
return Results.File(bytes, "image/png");
}
catch (ArgumentException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
catch (Exception ex)
{
// Hide engine details from clients; log the exception server-side if needed
return Results.StatusCode(500);
}
});
// Health check
app.MapGet("/health", () => Results.Ok(new { ok = true }));
app.Run();
Enkel testside
Drop dette ind wwwroot/index.html
at prøve API i en browser uden nogen front-end ramme.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Aspose.TeX LaTeX Demo</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
body{font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif;margin:2rem;line-height:1.4}
textarea{width:100%;height:8rem}
.row{display:flex;gap:1rem;align-items:flex-start;margin-top:1rem;flex-wrap:wrap}
.card{border:1px solid #ddd;border-radius:8px;padding:1rem;flex:1 1 320px}
img{max-width:100%;height:auto;border:1px solid #eee;border-radius:4px;background:#fff}
label{font-weight:600}
input[type=number]{width:6rem}
.muted{color:#666;font-size:.9rem}
.error{color:#b00020}
</style>
</head>
<body>
<h1>Real-time LaTeX to PNG with Aspose.TeX</h1>
<p class="muted">Type LaTeX math and click Render. The server returns a PNG image rendered by Aspose.TeX.</p>
<div class="card">
<label for="latex">LaTeX</label><br />
<textarea id="latex">x^2 + y^2 = z^2</textarea><br />
<label for="dpi">DPI</label>
<input id="dpi" type="number" min="72" max="600" value="200" />
<button id="btn">Render</button>
<div id="msg" class="error"></div>
</div>
<div class="row">
<div class="card">
<h3>Preview</h3>
<img id="preview" alt="Rendered equation will appear here" />
</div>
<div class="card">
<h3>cURL</h3>
<pre id="curl" class="muted"></pre>
</div>
</div>
<script>
const btn = document.getElementById('btn');
const latex = document.getElementById('latex');
const dpi = document.getElementById('dpi');
const img = document.getElementById('preview');
const msg = document.getElementById('msg');
const curl = document.getElementById('curl');
function updateCurl() {
const payload = JSON.stringify({ latex: latex.value, dpi: Number(dpi.value) }, null, 0);
curl.textContent =
`curl -s -X POST http://localhost:5000/api/latex/png \
-H "Content-Type: application/json" \
-d '${payload}' --output out.png`;
}
updateCurl();
[latex, dpi].forEach(el => el.addEventListener('input', updateCurl));
btn.addEventListener('click', async () => {
msg.textContent = '';
img.src = '';
try {
const payload = { latex: latex.value, dpi: Number(dpi.value) };
const res = await fetch('/api/latex/png', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
msg.textContent = err.error || `Error ${res.status}`;
return;
}
const blob = await res.blob();
img.src = URL.createObjectURL(blob);
} catch (e) {
msg.textContent = 'Request failed.';
}
});
</script>
</body>
</html>
Udfør projektet
dotnet run
Open http://localhost:5000
eller http://localhost:5173
Afhængigt af din lanceringsprofil. Skriv en ekvation og klik på Render. Forhåndsvisning opdateringer med server-side PNG output.
Konfiguration og implementering noter
- **Aspose.TeX licens*Hvis du har en licensfil, skal du indstille den under opstart for at fjerne evalueringsgrænser.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- Cache beliggenhed *Rendereren skriver cache filer under
tex-cache/
På Linux eller containerede deployments kan du montere denne vej på en vedvarende volumen. rengør det på et tidsplan, hvis det er nødvendigt.
- Cache beliggenhed *Rendereren skriver cache filer under
** Forespørgselsstørrelse begrænsninger**Eksemplet kapsler forespørgselsstørrelse på 256 KB. Øge, hvis du understøtter større indtægter.
- Cross-original adgang*Hvis du serverer API’en fra en anden oprindelse end webstedet, aktiverer du CORS i overensstemmelse.
Sikkerhedskontrolliste
- Tjenesten afviser potentielt farlige kommandoer som
\input
,\include
, og\write18
Hold tillægslisten fast og hold præamblen fast iBuildStandaloneDocument
. - Begræns indgangslængden til at blokere patologiske lader.
- Render i et unikt arbejdskode efter anmodning og sletter katalogen efter succes.
- Overvej hastighedsbegrænsning på det omvendte proxy- eller API-gatewayniveau for offentlige steder.
Udførelsestips
- Brug disk cache for at undgå at recomputere de samme ekvationer. prøven hasher LaTeX plus DPI til at deduplikere resultaterne.
- Hold DPI mellem 150 og 300 for de fleste UI behov. Højere DPI øges gør tid og billedstørrelse.
- Opvarm appen ved at udgive en almindelig formel ved start, hvis du vil, at den første brugerforespørgsel skal være øjeblikkelig.
- Hvis du har brug for vektorudgang til zoombar indhold, skal du skifte til
SvgSaveOptions
ogSvgDevice
, så indlej SVG. Resten af rørledningen er den samme.
Troubleshooting
- Blank udgang eller fejlTjek serverlogs. Hvis LaTeX bruger pakker uden for den faste preamble, fjerne dem.
\usepackage
brugerindtægter af design. - Klipning eller store marginerDen
standalone
dokumentklasse normalt trækker marginerne tæt. Hvis du stadig ser ekstra plads, vrag med\[
og\]
for at vise matematik eller fjerne dem for inline størrelse. - Skrevet tekst *Øget
PngSaveOptions.Resolution
Ved 200 til 300 DPI ser de fleste UI-sager krisp ud.
- Skrevet tekst *Øget
API hurtig reference anvendt i kode
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: skaber muligheder for Object LaTeX-motorenPngSaveOptions
: kontrollerer PNG-udledning medResolution
ogDeviceWritesImages
ImageDevice
: PNG-buffere resulterer i hukommelse, nårDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
Kompilere den.tex
filer ind i enhedenInputFileSystemDirectory
ogOutputFileSystemDirectory
: definere arbejdsdialoger for input og output
Med disse byggeblokker kan din ASP.NET-app give LaTeX på efterspørgsel, cache-resultater og tjene crisp PNG-ekvenser pålideligt.