Hướng dẫn này cho thấy làm thế nào để thêm tập trình toán LaTeX thời gian thực vào một ứng dụng ASP.NET bằng cách sử dụng Aspose.TEX cho .NET. Bạn sẽ xây dựng một API web nhỏ mà chấp nhận nhập LaTex, chuyển đổi nó sang PNG, trả về các byte hình ảnh với loại nội dung chính xác, và cache kết quả trên đĩa.
Những gì bạn sẽ xây dựng
Một ứng dụng ASP.NET Core với:
Endpoint
POST /api/latex/png
chấp nhận LaTeX và trả lại hình ảnh PNGTrang HTML đơn giản nơi người dùng nhập một sự đồng bằng và xem một bản xem trước trực tiếp
Disk caching chìa khóa bằng hash nội dung và DPI
Chứng nhận đầu vào cơ bản và thư mục làm việc sandboxed
Bạn có thể sao chép mã và chạy nó như vậy.
Nguyên tắc
Windows, Linux, hoặc macOS với .NET 6 hoặc mới hơn
Visual Studio 2022 hoặc VS Code* với phần mở rộng C#
gói NuGet Aspose.TeX
dotnet add package Aspose.TeX
ASPOSE.TEX Giới thiệu TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
, và OutputFileSystemDirectory
Bạn sẽ sử dụng chúng để chuyển LaTeX sang PNG.
Dự án Layout
Tạo một ASP.NET Core Web API dự án, sau đó thêm một dịch vụ nhẹ cộng với một trang HTML tối thiểu.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
Dịch vụ: LaTeX to PNG renderer
Dịch vụ này viết LaTeX cho một tạm thời .tex
file, chạy công việc Aspose.TeX, và trả về các byte PNG. Nó cũng thực hiện một cache đơn giản trên đĩa.
// 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: điểm kết thúc tối thiểu ASP.NET Core
Điều này xác định một hợp đồng JSON, đăng ký nhà cung cấp, và tiết lộ một POST
Điểm kết thúc trả về 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();
Trang kiểm tra đơn giản
Drop this vào wwwroot/index.html
để thử API trong một trình duyệt mà không có bất kỳ 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>
chạy dự án
dotnet run
Open http://localhost:5000
hoặc http://localhost:5173
Tùy thuộc vào hồ sơ khởi động của bạn. Nhập một sự đồng bằng và nhấp vào Render. Các bản cập nhật trước với kết quả PNG bên máy chủ.
Thiết lập và triển khai ghi chú
- Giấy phép Aspose.TeX*Nếu bạn có một tệp giấy phép, hãy thiết lập nó trong thời gian khởi động để loại bỏ giới hạn đánh giá.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- Vị trí cache*Renderer viết tệp cache dưới
tex-cache/
Trong root nội dung. trên Linux hoặc containerized deployments bạn có thể lắp đặt con đường này trên một khối lượng vĩnh viễn. làm sạch nó trên lịch nếu cần thiết.
- Vị trí cache*Renderer viết tệp cache dưới
- Giới hạn yêu cầu*Ví dụ cáp yêu cầu kích thước là 256 KB. Tăng nếu bạn hỗ trợ nhập lớn hơn.
Cross-origin truy cậpNếu bạn phục vụ API từ một nguồn khác với trang web, kích hoạt CORS theo đó.
Danh sách kiểm tra an ninh
- Dịch vụ từ chối lệnh nguy hiểm tiềm năng như
\input
,\include
, và\write18
• Giữ danh sách cho phép chặt chẽ và giữ tiền mặt ổn định trongBuildStandaloneDocument
. - Giới hạn chiều dài nhập để chặn các khoản thanh toán bệnh lý.
- Đăng nhập vào một thư mục làm việc độc đáo theo yêu cầu và xóa thư viện sau khi thành công. mẫu chỉ giữ PNG được cache.
- Hãy xem xét giới hạn tốc độ ở cấp proxy hoặc cổng API ngược cho các trang web công cộng.
Hiệu suất tips
- Sử dụng cache ổ đĩa ** để tránh recomputing các phương pháp tương tự. mẫu hash LaTeX plus DPI để deduplicate kết quả.
- Giữ DPI giữa 150 và 300 cho hầu hết các nhu cầu UI. tăng DPI cao hơn làm cho thời gian và kích thước hình ảnh.
- Làm nóng ứng dụng bằng cách cung cấp một công thức phổ biến tại startup nếu bạn muốn yêu cầu người dùng đầu tiên là ngay lập tức.
- Nếu bạn cần phát hành vector cho nội dung có thể zoom, chuyển sang
SvgSaveOptions
vàSvgDevice
, sau đó nhúng SVG. phần còn lại của đường ống là tương tự.
Troubleshooting
- Blanck output hoặc lỗiKiểm tra hồ sơ máy chủ.Nếu LaTeX sử dụng các gói bên ngoài preamble cố định, loại bỏ chúng.
\usepackage
trong User input by design. - Clipping hoặc margins lớncủa The
standalone
lớp tài liệu thường gửi ranh giới chặt chẽ. nếu bạn vẫn thấy không gian bổ sung, nhúng với\[
và\]
để hiển thị toán hoặc loại bỏ chúng cho kích thước inline. - Bài viết được lưu trữ *tăng
PngSaveOptions.Resolution
Tại 200 đến 300 DPI hầu hết các trường hợp UI trông crisp.
- Bài viết được lưu trữ *tăng
API nhanh tham chiếu được sử dụng trong mã
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: tạo các tùy chọn cho động cơ Object LaTeXPngSaveOptions
: kiểm soát sản xuất PNG vớiResolution
vàDeviceWritesImages
ImageDevice
: buffers PNG kết quả trong bộ nhớ khiDeviceWritesImages = false
TeXJob(texPath, device, options).Run()
• Tóm tắt The.tex
file vào thiết bịInputFileSystemDirectory
vàOutputFileSystemDirectory
: xác định các thư mục làm việc cho input và output
Với các khối xây dựng này, ứng dụng ASP.NET của bạn có thể cung cấp cho LaTeX theo nhu cầu, kết quả cache và phục vụ các so sánh crisp PNG một cách đáng tin cậy.