Acest ghid arată cum să adăugați real-time LaTeX matematică rendering într-o aplicație ASP.NET folosind Aspose.Tex pentru .NET. Veți construi o API web mică care acceptă intrarea LaTEX, o întoarce la PNG, returnează byte de imagine cu tipul corect de conținut și cache rezultatele pe disc.
Ce vei construi
O aplicație ASP.NET Core cu:
Endpoint
POST /api/latex/png
care acceptă LaTeX și returnează o imagine PNGPagină HTML simplă în care utilizatorii tipăresc o ecuație și văd un preview live
Caching-ul cu cheie prin un hash de conținut și DPI
Validație de intrare de bază și directorii de lucru sandboxed
Puteți să copiați codul și să-l executați așa cum este.
Prevederile
Windows, Linux sau macOS cu .NET 6 sau mai târziu
Visual Studio 2022 sau VS Code* cu extensie C#
Pachetul NuGet Aspose.TeX
dotnet add package Aspose.TeX
Aspose.TeX expoziții TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, şi OutputFileSystemDirectory
Veți folosi aceste pentru a transfera LaTeX la PNG.
Layout proiect
Creați un proiect ASP.NET Core Web API** și adăugați apoi un serviciu ușor și o pagină minimă HTML.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Serviciul: LaTeX la PNG Render
Acest serviciu scrie LaTeX la o .tex
fișier, funcționează activitatea Aspose.TeX și returnează bite PNG. De asemenea, implementează un cache simplu pe disc.
// 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();
}
}
API-ul web: un punct de finisare ASP.NET
Aceasta definește un contract JSON, înregistrează renderul și expune un POST
Punctul final care returnează 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();
Pagina de testare simplă
Aruncați acest lucru în wwwroot/index.html
pentru a încerca API-ul într-un browser fără nici un cadru front-end.
<!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>
Executați proiectul
dotnet run
Open http://localhost:5000
sau http://localhost:5173
În funcție de profilul dvs. de lansare. tipărați o ecuație și faceți clic pe Render. actualizările preview cu ieșirea PNG de pe server.
Notele de configurare și de implementare
- Licența Aspose.TeX*Dacă aveți un fișier de licență, configurați-l în timpul start-up-ului pentru a elimina limitele de evaluare.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- Localizarea cache*Renderul scrie fișierele cache sub
tex-cache/
în rădăcina conținutului. pe Linux sau deploimente containerizate puteți monta acest drum pe un volum persistent. curățați-l într-un program dacă este necesar.
- Localizarea cache*Renderul scrie fișierele cache sub
- Limite de cerere*Exemplul cuprinde dimensiunea cererii la 256 KB. Crește dacă susțineți intrări mai mari.
- Accesul prin intermediul originii*Dacă serveșteți API-ul dintr-o origine diferită de site, activați CORS în consecință.
Lista de verificare de securitate
- Serviciul respinge comenzi potențial periculoase precum
\input
,\include
, şi\write18
Păstrați lista de permisiuni strânsă și mențineți preamblul fixat înBuildStandaloneDocument
. - Restrictați lungimea intrării pentru a bloca sarcinile patologice.
- Intră într-un director de lucru unic la cerere și ștergeți directorul după succes. eșantionul păstrează doar PNG cache.
- Gândiți-vă la limitarea ratei la nivelul de proxy invers sau API pentru site-urile publice.
Tipuri de performanță
- Utilizați cache-ul de disc ** pentru a evita recomputarea aceluiași ecuație.
- Păstrați DPI între 150 și 300 pentru cele mai multe nevoi de UI. DPI mai mare crește în timp și în dimensiunea imaginii.
- Încălziți aplicația oferind o formulă comună la start-up dacă doriți ca prima cerere de utilizator să fie instant.
- Dacă aveți nevoie de o ieșire vector pentru conținut zoomabil, schimbați la
SvgSaveOptions
şiSvgDevice
, apoi adăugați SVG. restul tubului este același.
Troubleshooting
- Răspunsuri sau greșeliVerificați jurnalele serverului. dacă LaTeX utilizează pachete în afara preambulului fix, eliminați-le.
\usepackage
Intrarea utilizatorului prin design. - Clipuri sau marje maripe care
standalone
clasa de documente trimite de obicei marginile strâns. dacă vedeți încă spațiu suplimentar, înfășurați cu\[
şi\]
Pentru a afișa matematică sau pentru a le îndepărta pentru dimensiunea inline. - Textul păstrat*creşterea
PngSaveOptions.Resolution
La 200 până la 300 DPI, cele mai multe cazuri de UI arată crisp.
- Textul păstrat*creşterea
API de referință rapidă utilizată în cod
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
Creează opțiuni pentru motorul Object LaTeXPngSaveOptions
Controlul de producţie PNG cuResolution
şiDeviceWritesImages
ImageDevice
: bufferii PNG rezultă în memorie atunci cândDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• Compilarea de.tex
Fișier în dispozitivInputFileSystemDirectory
şiOutputFileSystemDirectory
: definește directorii de lucru pentru intrare și ieșire
Cu aceste blocuri de construcție, aplicația ASP.NET vă poate oferi LaTeX pe cerere, rezultate de cache și servește ecuațiile crisp PNG în mod fiabil.