Bu kılavuz, bir ASP.NET uygulamasını Aspose.TeX for .NET kullanarak gerçek zamanlı LaTEX matematiksel rendering nasıl ekleyeceğinizi gösterir.Latex girişini kabul eden küçük bir web API’si oluşturacaksınız, PNG‘ye dönüştürür, doğru içerik türü ile görüntü bytesini iade eder ve diskin sonuçlarını cache eder.
Ne inşa edeceğin
Bir ASP.NET Core uygulaması ile:
Endpoint
POST /api/latex/png
LaTeX’i kabul eden ve PNG görüntüsünü iade edenBasit bir HTML sayfası, kullanıcıların bir eşitliği yazıp canlı bir önizleme görmeleri
Disk caching anahtarı bir içerik hash ve DPI ile
Temel giriş doğrulama ve sandboxed çalışma dizinleri
Kodu kopyalayabilir ve olduğu gibi çalıştırabilirsiniz.
Ön koşullar
Windows, Linux veya macOS ile .NET 6 veya sonraki
Visual Studio 2022 veya VS Kodu* C# uzantısı ile
NuGet paketi Aspose.TeX
dotnet add package Aspose.TeX
ASPOSE.TEX Yorumları TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, ve OutputFileSystemDirectory
Bunları LaTeX’i PNG’ye dönüştürmek için kullanacaksınız.
Proje Layout
Bir ASP.NET Core Web API projesi oluşturun, daha sonra hafif bir servis artı minimum bir HTML sayfasını ekleyin.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Hizmet: LaTeX to PNG renderer
Bu hizmet LaTeX’i geçici bir .tex
Dosya, Aspose.TeX işini çalıştırır ve PNG baytlarını geri getirir.
// 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
Bu, bir JSON sözleşmesini tanımlar, sunucuyu kaydeder ve bir POST
Son nokta PNG’yi geri getiriyor.
// 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();
Basit Test Sayfası
Bunu içine atın wwwroot/index.html
Bir tarayıcıda herhangi bir front-end çerçevesi olmadan API’yi denemek.
<!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>
Projeyi çalıştırın
dotnet run
Open http://localhost:5000
veya http://localhost:5173
Başlangıç profilinize bağlı olarak. bir eşitliği girin ve Render’ı tıklatın. sunucunun yanındaki PNG çıkışı ile önceden görüntülenen güncellemeler.
Konfigürasyon ve dağıtım notları
- Aspose.TeX lisansıBir lisans dosyası varsa, değerlendirme sınırlarını kaldırmak için başlangıç sırasında ayarlayın.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
*Cache Konumu *Renderer cache dosyalarını aşağıda yazar
tex-cache/
Linux veya konteynerli dağıtımlarda bu yolu kalıcı bir hacme monte edebilirsiniz. gerekirse bir programda temizleyin.- İhtiyaç boyutu sınırları *Örnek kapak talep büyüklüğü 256 KB’dir. daha büyük girişleri desteklerseniz arttırın.
- Çapraz orijinal erişim*Site’den farklı bir kökenli API’yi kullanırsanız, CORS’i buna göre etkinleştirin.
Güvenlik kontrol listesi
- Hizmet, potansiyel olarak tehlikeli komutları reddediyor
\input
,\include
, ve\write18
İzin listesini sıkı tutun ve ön çerçeveyi ayarlayınBuildStandaloneDocument
. - Patolojik ödeme yükünü engellemek için giriş uzunluğunu sınırlayın.
- Başarılı olduktan sonra tek bir çalışma dizinine girin ve başvuru başına ve dizini silin. örnek sadece kaydedilen PNG tutar.
- Halk siteleri için ters proxy veya API kapı düzeyinde oran sınırlaması düşünün.
performans tipleri
- Aynı eşitlikleri yeniden hesaplamaktan kaçınmak için disk cache kullanın. örnek hash LaTeX artı DPI sonuçları deduplicate etmek için.
- DPI‘yi çoğu UI ihtiyacı için 150 ile 300 arasında tutun. daha yüksek DPI artışı zaman ve görüntü boyutunu gösterir.
- İlk kullanıcı isteği anında olmasını istiyorsanız başlangıçta ortak bir formül sunarak uygulamayı ısıtın.
- Zoom içeriği için vektör çıkışına ihtiyacınız varsa,
SvgSaveOptions
veSvgDevice
, sonra SVG’yi yerleştirin. geri kalan boru hattı aynıdır.
Troubleshooting
- Blank çıkış veya hatalarSunucu günlükleri kontrol edin. eğer LaTeX sabit preamble dışındaki paketleri kullanırsa, bunları kaldırın.
\usepackage
Kullanıcı içeriği tasarımı ile. - Küçük veya büyük marjlarThe için
standalone
belge sınıfı genellikle marjları sıkı bir şekilde gönderir. eğer hala ek alan görüyorsanız,\[
ve\]
Görüntüleme için veya bunları inline boyutu için kaldırın. - Yazılı Yazılar *Büyüme
PngSaveOptions.Resolution
200 ile 300 DPI arasında çoğu UI vakası çirkin görünüyor.
- Yazılı Yazılar *Büyüme
API kodda kullanılan hızlı referans
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: Object LaTeX motor için seçenekler oluştururPngSaveOptions
: PNG çıkış kontrolü ileResolution
veDeviceWritesImages
ImageDevice
: buffers PNG hafızada sonuçlanırDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• Toplayın The.tex
Dosyayı cihazın içineInputFileSystemDirectory
veOutputFileSystemDirectory
: Giriş ve çıkış için çalışma dizinlerini tanımlayın
Bu yapı blokları ile ASP.NET uygulamanız LaTeX’i talep üzerine, cache sonuçlarına ve crisp PNG eşitliklerine güvenilir bir şekilde sunabilir.