Panduan ini menunjukkan bagaimana untuk menambahkan real-time LaTeX matematika rendering ke aplikasi ASP.NET menggunakan Aspose.Tex untuk .NET. Anda akan membangun API web kecil yang menerima input LaTEX, mengembalikannya ke PNG, kembali byte gambar dengan jenis konten yang benar, dan cache hasil pada cakera.
Apa yang akan dibangun
Sebuah aplikasi ASP.NET Core dengan:
Endpoint
POST /api/latex/png
yang menerima LaTeX dan mengembalikan gambar PNGHalaman HTML sederhana di mana pengguna mengetik persamaan dan melihat preview langsung
Disk caching dikunci dengan konten hash dan DPI
Validasi input dasar dan direktori kerja sandboxed
Anda dapat menyalin kode dan melakukannya seperti yang ada.
Persyaratan
Windows, Linux, atau macOS dengan .NET 6 atau lebih baru
Visual Studio 2022 atau VS Code* dengan ekstensi C#
Paket NuGet Aspose.TeX
dotnet add package Aspose.TeX
Aspose.TeX eksposur TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, dan OutputFileSystemDirectory
Anda akan menggunakan ini untuk memberikan LaTeX kepada PNG.
Layout proyek
Buat ASP.NET Core Web API proyek, kemudian tambahkan layanan ringan ditambah halaman HTML minimal.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Layanan: LaTeX untuk PNG renderer
Layanan ini menulis LaTeX untuk sementara .tex
file, menjalankan pekerjaan Aspose.TeX, dan mengembalikan byte PNG. Ini juga menerapkan cache sederhana pada cakera.
// 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: titik akhir ASP.NET
Ini mendefinisikan kontrak JSON, mendaftarkan renderer, dan mendedahkan POST
Titik akhir yang mengembalikan 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();
Halaman Ujian Mudah
Letakkan ini ke dalam wwwroot/index.html
untuk mencoba API dalam browser tanpa framework 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>
Lakukan proyek
dotnet run
Open http://localhost:5000
atau http://localhost:5173
Tipe persamaan dan klik Render. Preview update dengan server-side PNG output.
Konfigurasi dan Pemasangan Notis
- *Lisensi Aspos.TeXJika Anda memiliki file lisensi, tetapkan selama startup untuk menghapus batas evaluasi.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- lokasi penyimpanan *Renderer menulis file cache di bawah
tex-cache/
Pada Linux atau deployments yang terkontaminasi, Anda dapat mengatur jalur ini pada volume yang berkelanjutan.
- lokasi penyimpanan *Renderer menulis file cache di bawah
Batas ukuran permintaanContoh tab permintaan ukuran 256 KB. Meningkatkan jika Anda mendukung input yang lebih besar.
- Akses melalui sumber*Jika Anda melayani API dari asal yang berbeda dari situs, mengaktifkan CORS sesuai.
Checklist Keselamatan
- Layanan menolak perintah yang berpotensi berbahaya seperti
\input
,\include
, dan\write18
Tetap ketat dan simpan preamble diBuildStandaloneDocument
. - Mengehadkan panjang input untuk menghalangi beban patologis.
- Mendapatkan direktori kerja yang unik atas permintaan dan menghapus catatan setelah sukses. sampel hanya menyimpan PNG tersembunyi.
- Pertimbangkan pembatasan kadar pada tingkat proxy terbalik atau gateway API untuk situs publik.
Tips kinerja
- Gunakan cache cakera ** untuk menghindari mengkomputasi persamaan yang sama. sampel hash LaTeX plus DPI untuk deduplicate hasil.
- Tetap DPI antara 150 dan 300 untuk sebagian besar kebutuhan UI. DPI yang lebih tinggi meningkatkan waktu dan ukuran gambar.
- Pemanas aplikasi dengan memberikan formula umum di startup jika Anda ingin permintaan pengguna pertama menjadi instan.
- Jika Anda membutuhkan output vektor untuk konten zoom, beralih ke
SvgSaveOptions
danSvgDevice
, kemudian dimasukkan ke SVG. selebihnya jalur adalah sama.
Troubleshooting
- Kecelakaan atau kesilapanPeriksa log server.Jika LaTeX menggunakan paket di luar preamble tetap, menghapusnya.
\usepackage
dalam input pengguna oleh desain. - Kecepatan atau margin besardan yang
standalone
Kelas dokumen biasanya mengirimkan margin dengan ketat.Jika Anda masih melihat ruang tambahan, campurkan dengan\[
dan\]
untuk memaparkan matematika atau menghapusnya untuk ukuran inline. - Teks yang disimpan *Meningkatkan
PngSaveOptions.Resolution
Pada 200 hingga 300 DPI, kebanyakan kasus UI terlihat crisp.
- Teks yang disimpan *Meningkatkan
API referensi cepat yang digunakan dalam kode
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: menciptakan opsi untuk mesin Object LaTeXPngSaveOptions
: mengontrol output PNG denganResolution
danDeviceWritesImages
ImageDevice
: buffer PNG hasil dalam memori ketikaDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• Mengkompilasi.tex
File ke dalam perangkatInputFileSystemDirectory
danOutputFileSystemDirectory
: menentukan direktori kerja untuk input dan output
Dengan blok bangunan ini, aplikasi ASP.NET Anda dapat memberikan LaTeX pada permintaan, hasil cache, dan melayani persamaan crisp PNG dengan aman.