Denna guide visar hur man lägger till real-time LaTeX matematisk rendering till en ASP.NET-app med hjälp av Aspose.Tex för .NET. Du kommer att bygga en liten web-API som accepterar Latex input, returnerar den till PNG*, returnerar bildbyten med rätt innehållstyp och cache resultat på skivan.
Vad du kommer att bygga
En ASP.NET Core app med:
Endpoint
POST /api/latex/png
som accepterar LaTeX och returnerar en PNG-bildEnkel HTML-sida där användare skriver en ekvation och ser en live preview
Disk caching med en innehållshash och DPI
Grundläggande inputvalidering och sandboxade arbetsdialoger
Du kan kopiera koden och köra den som den är.
förutsättningar
Windows, Linux eller macOS med .NET 6 eller senare
Visual Studio 2022 eller VS Code* med C# förlängning
NuGet paket Aspose.TeX
dotnet add package Aspose.TeX
ASPOSE.TEX utställningar TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, och OutputFileSystemDirectory
Du kommer att använda dessa för att överföra LaTeX till PNG.
Projekt layout
Skapa ett ASP.NET Core Web API-projekt och lägg sedan till en lättvikttjänst plus en minimal HTML-sida.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Service: LaTeX till PNG renderer
Denna tjänst skriver LaTeX till en tillfällig .tex
filen, kör Aspose.TeX jobb, och returnerar PNG byter. Det implementerar också en enkel påsk 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: Minimal ASP.NET Core Endpoint
Detta definierar ett JSON-avtal, registrerar renderaren och visar en POST
Endpoint som returnerar 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 testsida
Hämta detta in i wwwroot/index.html
Försök API i en webbläsare utan någon front-end ram.
<!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>
Kör projektet
dotnet run
Open http://localhost:5000
eller http://localhost:5173
Beroende på din startprofil. Skriv en ekvation och klicka på Render. Förhandsuppdateringen med PNG-utgång på servern.
Konfiguration och implementering noter
- **Aspose.TeX licens*Om du har en licensfil, ställ in den under start för att ta bort utvärderingsgränser.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
*Cache läge *Renderaren skriver cache-filer under
tex-cache/
På Linux eller behållare kan du montera den här vägen på en beständig volym. Rengör den på ett schema om det behövs.** Begränsningar för begäran storlek**Exempelboksen begär storlek på 256 KB. Öka om du stöder större inmatningar.
- Tillgång mellan ursprung*Om du serverar API från en annan ursprung än webbplatsen, aktiverar du CORS i enlighet med detta.
Säkerhetskontrolllista
- Tjänsten avvisar potentiellt farliga kommandon som
\input
,\include
, och\write18
Håll tillståndslistan fast och håll preambeln fast iBuildStandaloneDocument
. - Begränsa ingångslängden för att blockera patologiska laddningar.
- Hämta i en unik arbetsbok per begäran och radera katalogen efter framgång. Provet behåller endast cached PNG.
- Tänk på hastighetsbegränsning på den omvända proxy- eller API-gatewaynivån för offentliga webbplatser.
Performance tips
- Använd skiva cache för att undvika att recomputera samma ekvationer. Proverna hashar LaTeX plus DPI till att dubbla resultaten.
- Håll DPI mellan 150 och 300 för de flesta UI-behov.
- Uppvärm appen genom att göra en vanlig formel vid start om du vill att den första användarförfrågan ska vara omedelbar.
- Om du behöver vektorutgång för zoombar innehåll, växla till
SvgSaveOptions
ochSvgDevice
, sedan in SVG. Resten av rörledningen är densamma.
Troubleshooting
- Blank utgång eller felKontrollera serverloggar. Om LaTeX använder paket utanför den fasta preambeln, ta bort dem.
\usepackage
Användarens inmatning genom design. - Clipping eller stora marginaleroch den
standalone
dokumentklassen skickar vanligtvis marginaler noggrant. om du fortfarande ser extra utrymme, vrid med\[
och\]
för att visa matematik eller ta bort dem för inline storlek. - Förvarad text *ökar
PngSaveOptions.Resolution
Vid 200 till 300 dpi ser de flesta UI-fall krisp.
- Förvarad text *ökar
API snabb referens som används i kod
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: skapar alternativ för Object LaTeX-motornPngSaveOptions
: kontrollerar PNG utgång medResolution
ochDeviceWritesImages
ImageDevice
: buffers PNG resulterar i minnet närDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• Kompilera den.tex
filer in i enhetenInputFileSystemDirectory
ochOutputFileSystemDirectory
: definiera arbetshandboken för input och output
Med dessa byggblock kan din ASP.NET-app leverera LaTeX på efterfrågan, cache-resultat och servera crisp PNG-jämförelser på ett tillförlitligt sätt.