Deze gids toont hoe u real-time LaTeX mathematische rendering toevoegt aan een ASP.NET-app met behulp van Aspose.TEX voor .NET. U bouwt een kleine web API die de LaTex-input accepteert, het rendert naar PNG, de beelden byten met de juiste inhoudstype retourneert en cache resultaten op de schijf.
Wat je zal bouwen
Een ASP.NET Core app met:
Endpoint
POST /api/latex/png
die LaTeX accepteert en een PNG-afbeelding retourneertEenvoudige HTML-pagina waar gebruikers een vergelijking invoeren en een live voorbeeld zien
Disk caching met een inhoudshash en DPI
Basic input validatie en sandboxed werkdirecties
U kunt de code kopiëren en draaien zoals het is.
Voorwaarden
Windows, Linux of macOS met .NET 6 of later
Visual Studio 2022 of VS Code* met C#-uitbreiding
NuGet pakket Aspose.TeX
dotnet add package Aspose.TeX
Aspose.TeX vertonen TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, en OutputFileSystemDirectory
U zult deze gebruiken om LaTeX naar PNG te geven.
Project Layout
Creëer een ASP.NET Core Web API project en voeg vervolgens een lichte service plus een minimaal HTML-pagina toe.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Service: LaTeX tot PNG renderer
Deze service schrijft de LaTeX naar een tijdelijke .tex
bestand, loopt de Aspose.TeX werk, en geeft de PNG byten terug. Het implementeert ook een eenvoudige cache op de schijf.
// 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: minimaal ASP.NET Core eindpunt
Dit definieert een JSON-overeenkomst, registreert de renderer en legt een POST
Het eindpunt dat PNG terugbrengt.
// 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();
Eenvoudige testpagina
Drop dit in wwwroot/index.html
Probeer de API in een browser zonder een front-end framework.
<!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>
Run het project
dotnet run
Open http://localhost:5000
of http://localhost:5173
Afhankelijk van uw startprofiel. Type een vergelijking en klik op Render. De voorbeeld updates met server-side PNG output.
Configuratie en implementatie noten
- Aspose.TeX licentieAls u een licentiebestand hebt, installeer het tijdens start-up om de beoordelingsgrens te verwijderen.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- Locatie van de cache*De renderer schrijft cache-bestanden onder
tex-cache/
In de contentroot. op Linux of containerized deployments kunt u deze route monteren op een blijvende hoeveelheid. schoon het in een schema indien nodig.
- Locatie van de cache*De renderer schrijft cache-bestanden onder
Vraag grootte beperkingenHet voorbeeld bevat een verzoekgrootte van 256 KB. Verhoog als u grotere inputs ondersteunt.
**Cross-origin toegang*Als u de API van een andere oorsprong dan de site dient, kunt u CORS op deze manier activeren.
Veiligheidschecklijst
- De service weigert potentieel gevaarlijke bestellingen zoals
\input
,\include
, en\write18
Houd de toelatingslist dicht en houd het preamble vast inBuildStandaloneDocument
. - Beperk de ingang lengte om pathologische lading te blokkeren.
- Verwijder in een unieke werkdirector per verzoek en verwijder de directory na succes. het monster houdt alleen de cached PNG.
- Overweeg tariefbeperkingen op het omgekeerde proxy- of API-gateway-niveau voor openbare sites.
Performance tips
- Gebruik disk cache om te voorkomen dat dezelfde vergelijkingen worden gecomputeerd.De sample hashes LaTeX plus DPI om de resultaten af te dupliceren.
- Houd DPI tussen 150 en 300 voor de meeste UI-behoeften.
- Verwarm de app door een gemeenschappelijke formule te geven bij start-up als u wilt dat de eerste gebruikersverzoek onmiddellijk is.
- Als u een vector-uitgang nodig hebt voor zoombare inhoud, wisselen
SvgSaveOptions
enSvgDevice
, vervolgens de SVG in. De rest van de pijpleiding is hetzelfde.
Troubleshooting
- Blank uitgang of foutenControleer de serverlogs.Als de LaTeX pakketten gebruikt buiten de vaste preamble, verwijder ze.
\usepackage
Gebruikersinvoer door ontwerp. - Clipping of grote margesDe
standalone
documentklasse stuurt meestal de marges nauw. als u nog steeds extra ruimte ziet, wrap met\[
en\]
voor het weergeven van materie of verwijder ze voor inline grootte. - Gehaalde tekst *verhogen
PngSaveOptions.Resolution
Bij 200 tot 300 DPI lijken de meeste UI-gevallen crisp.
- Gehaalde tekst *verhogen
API snelle referentie gebruikt in code
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: maakt opties voor de Object LaTeX-motorPngSaveOptions
: controleert PNG-uitgang metResolution
enDeviceWritesImages
ImageDevice
: buffers PNG resulteren in geheugen wanneerDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• Compileren van de.tex
bestanden in het apparaatInputFileSystemDirectory
enOutputFileSystemDirectory
: definieert de werkdirecties voor input en output
Met deze bouwblokken kan uw ASP.NET-app LaTeX op vraag, cache-resultaten en crisp PNG-equaties betrouwbaar aanbieden.