Tämä opas osoittaa, miten lisätä real-time LaTeX matemaattinen rendering ASP.NET-sovellukseen käyttämällä Aspose.TEX for .NET. Rakennat pienen web API, joka hyväksyy LaTex-tuloksen, palauttaa sen PNG, Palauttaa kuvan bytejä oikealla sisällön tyypillä ja tallentaa tulokset levylle.
Mitä rakennat
ASP.NET Core -sovellus sisältää:
Endpoint
POST /api/latex/png
joka hyväksyy LaTeX ja palauttaa PNG-kuvanYksinkertainen HTML-sivu, jossa käyttäjät kirjoittavat yhtälön ja näkevät elävän ennakoinnin
Disk caching avain sisältö hash ja DPI
Basic input validation ja sandboxed työsuunnitelmat
Voit kopioida koodin ja suorittaa sen niin kuin se on.
edellytykset
Windows, Linux tai macOS, jossa on .NET 6 tai uudempi
Visual Studio 2022 tai VS-koodi* C# laajennuksella
NuGet paketti Aspose.TeX
dotnet add package Aspose.TeX
ASPOSE.TEX esittelee TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, ja OutputFileSystemDirectory
Käytät näitä luovuttamaan LaTeX PNG:lle.
Hankkeen layout
Luo ASP.NET Core Web API -hanke ja lisää sitten kevytpalvelun sekä vähimmäismääräisen HTML-sivun.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Palvelu: LaTeX to PNG renderer
Tämä palvelu kirjoittaa LaTeX tilapäiseen .tex
tiedosto suorittaa Aspose.TeX-työn ja palauttaa PNG-bytit. Se myös toteuttaa yksinkertaisen levyn kaapin.
// 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: minimaalinen ASP.NET Core -työkohta
Tämä määrittelee JSON-sopimuksen, rekisteröi luovuttajan ja esittää POST
Lopullinen piste, joka palauttaa 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();
Yksinkertainen testisivu
Laita tämä sisään wwwroot/index.html
kokeilla API:tä selaimessa ilman eturintakehystä.
<!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äynnistä projekti
dotnet run
Open http://localhost:5000
tai http://localhost:5173
riippuen aloitusprofiili. Kirjoita yhtälö ja napsauta Render. Etusivu päivitykset palvelinpuolella PNG tuotto.
Konfigurointi ja käyttöönotto
- **Aspose.TeX lisenssi*Jos sinulla on lisenssi tiedosto, aseta se käynnistyksen aikana poistamaan arviointirajat.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- Kaappauksen sijainti*Renderer kirjoittaa cache-tiedostoja alla
tex-cache/
Sisällön juuressa.Linuxissa tai säiliöissä voit asentaa tämän polun pysyvällä määrällä.Puhdista se aikataulussa, jos tarvitset.
- Kaappauksen sijainti*Renderer kirjoittaa cache-tiedostoja alla
** Pyynnön koon rajat*Esimerkkinä kaappi pyytää kokoa 256 KB. Lisää, jos tuket suurempia tuloksia.
- Lähteiden välinen käyttöoikeus*Jos palvelet API:tä eri alkuperästä kuin sivusto, käytä CORS vastaavasti.
Turvallisuuden tarkastusluettelo
- Palvelu hylkää mahdollisesti vaarallisia komentoja, kuten
\input
,\include
, ja\write18
Pidä salliluettelo tiiviisti ja pidä ennakkoluulo kiinteänäBuildStandaloneDocument
. - Vähennä sisäänkäynnin pituutta estääkseen patologiset kuormitukset.
- Siirry ainutlaatuiseen työrekisteriin pyynnöstä ja poista rekisteri onnistumisen jälkeen. Näyte säilyttää vain piilotetun PNG: n.
- Harkitse tason rajoittamista vastakkaisella proxy- tai API-portti tasolla julkisille sivustoille.
suorituskyvyn vinkkejä
- Käytä Disk Cache estääkseen samojen yhtälöiden kompensoimisen. näytteen hashes LaTeX plus DPI deduplicate tuloksia.
- Pidä DPI välillä 150 ja 300 useimmille UI-tarpeille. korkeampi DPI lisää aikaa ja kuvan koon.
- Lämmitä sovellus tarjoamalla yhteinen kaava startupissa, jos haluat, että ensimmäinen käyttäjän pyyntö on välitön.
- Jos tarvitset vektorin tuontia zoomitavalle sisällölle, vaihda
SvgSaveOptions
jaSvgDevice
, sitten sisällytetään SVG. Loput putki on sama.
Troubleshooting
- Pienet tulokset tai virheet*Tarkista palvelimen arkistoja.Jos LaTeX käyttää paketteja kiinteän etukäteen, poista ne.
\usepackage
Käyttäjän sisällöt suunnittelun mukaan.
- Pienet tulokset tai virheet*Tarkista palvelimen arkistoja.Jos LaTeX käyttää paketteja kiinteän etukäteen, poista ne.
- Liuku tai suuret marginaalit*Se on
standalone
asiakirjan luokka yleensä lähettää marginaaleja tiukasti. Jos näet ylimääräistä tilaa, pyyhkiä\[
ja\]
Näytä matemaatteja tai poista ne inline-kokoon.
- Liuku tai suuret marginaalit*Se on
- Käytetyt tekstit *kasvaa
PngSaveOptions.Resolution
200–300 DPI: ssä useimmat UI-tapaukset näyttävät pahalta.
- Käytetyt tekstit *kasvaa
API nopea viittaus käytetään koodiin
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: luo vaihtoehtoja Object LaTeX-moottoriinPngSaveOptions
: PNG-tuotantoa hallitseeResolution
jaDeviceWritesImages
ImageDevice
PNG:n tulokset muistiin, kunDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• yhdistää se.tex
tiedostoa laitteeseenInputFileSystemDirectory
jaOutputFileSystemDirectory
: määritellään tulon ja tuonnin työsuunnitelmat
Näiden rakennusblokkien ansiosta ASP.NET-sovellus voi tuottaa LaTeX:tä kysynnän, tallennustuloksia ja palvella crisp PNG-tunnuksia luotettavasti.