이 가이드는 Aspose.TeX for .NET를 사용하여 ASP.NET 애플리케이션에 real-time LaTEX 수학 프레젠테이션을 추가하는 방법을 보여줍니다.당신은 작은 웹 API를 구축 할 것입니다 라텍스 입력을 받아들이고, 그것을 PNG로 반환하고, 올바른 콘텐츠 유형으로 이미지를 바이트로 돌려주며, 디스크에 결과를 캐시합니다.
무엇을 건설할 것인가
ASP.NET 코어 앱은 다음과 같습니다:
Endpoint
POST /api/latex/png
LaTeX를 받아들이고 PNG 이미지를 반환합니다.사용자가 평등을 입력하고 라이브 프리뷰를 볼 수있는 간단한 HTML 페이지
디스크 캐싱은 콘텐츠 해시와 DPI로 키를 둔다.
기본 입력 검증 및 샌드 박스 작업 디렉토리
코드를 복사하고 그대로 실행할 수 있습니다.
원칙
윈도우, 리눅스 또는 macOS .NET 6 또는 이후
Visual Studio 2022 또는 VS 코드* C# 확장
NuGet 패키지 Aspose.TeX
dotnet add package Aspose.TeX
ASPOSE.TEX 소개 TeXOptions
, TeXConfig.ObjectLaTeX
, PngSaveOptions
, ImageDevice
, TeXJob
, InputFileSystemDirectory
그리고, 그리고 OutputFileSystemDirectory
이들을 사용하여 LaTeX를 PNG로 전달합니다.
프로젝트 레이아웃
ASP.NET Core Web API 프로젝트를 만들고 가벼운 서비스와 최소한의 HTML 페이지를 추가합니다.
AsposeTexDemo/
Program.cs
Services/
LatexRenderer.cs
wwwroot/
index.html
서비스: LaTeX to PNG 렌더
이 서비스는 LaTeX를 일시적으로 작성합니다. .tex
파일, Aspose.TeX 작업을 실행하고 PNG 바이트를 반환합니다.그것은 또한 디스크에 간단한 캐시를 구현.
// 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 : 최소 ASP.NET 코어 엔드포인트
이것은 JSON 계약을 정의하고, 렌더를 기록하며, POST
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();
간단한 테스트 페이지
이걸 내려놓고 wwwroot/index.html
프론트 엔드 프레임없이 브라우저에서 API를 시도하십시오.
<!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>
프로젝트 실행
dotnet run
Open http://localhost:5000
또는 http://localhost:5173
당신의 시작 프로필에 따라.평등을 입력하고 Render를 클릭합니다.Server-side PNG 출력으로 미리 보기 업데이트.
설정 및 배치 노트
- 아스포스.텍스 라이센스*라이센스 파일이 있는 경우 스타트업 중에 설정하여 평가 제한을 제거합니다.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
- 캐시 위치*렌더는 아래에 캐시 파일을 작성합니다.
tex-cache/
콘텐츠 뿌리에서.Linux 또는 컨테이너 배치에서 이 경로를 지속적인 볼륨으로 설치할 수 있습니다.필요한 경우 일정에 청소합니다.
- 캐시 위치*렌더는 아래에 캐시 파일을 작성합니다.
- 요청 크기 제한*예제 캡 크기를 256 KB로 요청합니다.더 큰 입력을 지원하는 경우 증가하십시오.
Cross-original 액세스귀하가 사이트와 다른 출처에서 API를 제공하는 경우, 해당 CORS를 활성화합니다.
보안 체크리스트
- 서비스는 잠재적으로 위험한 명령을 거부합니다.
\input
,\include
그리고, 그리고\write18
허용 목록을 단단히 유지하고 프레임블을 고정시키십시오.BuildStandaloneDocument
. - 삽입 길이를 제한하여 병리학적 지불을 차단합니다.
- 요청에 따라 독특한 작업 디렉토리로 내보내고 성공한 후에 리더를 삭제합니다.
- 공공 사이트에 대한 반대 프로시 또는 API 포트웨이 수준에서 속도 제한을 고려하십시오.
성과 타이프
- 디스크 캐시를 사용하여 동일한 방정식을 재구성하는 것을 피하십시오. 샘플은 LaTeX 플러스 DPI를 해시하여 결과를 복제합니다.
- 대부분의 UI 요구 사항을 위해 DPI를 150 ~ 300 사이에 유지하십시오.더 높은 DPI는 시간과 이미지 크기를 나타냅니다.
- 첫 번째 사용자 요청이 즉시되기를 원하는 경우 스타트업에서 일반적인 수식을 제공함으로써 앱을 따뜻하게합니다.
- Zoomable 콘텐츠를 위한 벡터 출력을 필요로 하는 경우,
SvgSaveOptions
그리고SvgDevice
, 그 다음 SVG를 삽입합니다. 나머지 파이프 라인은 동일 합니다.
Troubleshooting
- ** 블랙 출력 또는 오류**서버 로그를 확인하십시오.LaTeX가 고정 사전 밖의 패키지를 사용하는 경우, 그들을 제거합니다.
\usepackage
사용자 입력에 따라 디자인. - 클립 또는 큰 마진*그들의
standalone
문서 클래스는 일반적으로 마진을 밀접하게 전송합니다. 여전히 추가 공간을 볼 수 있다면,\[
그리고\]
매트를 표시하거나 인라인 크기로 제거하십시오.
- 클립 또는 큰 마진*그들의
- 녹음된 문서*늘어나는
PngSaveOptions.Resolution
200 ~ 300 DPI에서 대부분의 UI 사례는 크리스프처럼 보입니다.
- 녹음된 문서*늘어나는
API 빠른 참조 코드에서 사용
TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX)
: Object LaTeX 엔진에 대한 옵션을 만듭니다.PngSaveOptions
: PNG 생산을 제어Resolution
그리고DeviceWritesImages
ImageDevice
: PNG 버퍼는 메모리에서 발생할 때DeviceWritesImages = false
TeXJob(texPath, device, options).Run()
: 컴파일 The.tex
파일이 장치에 들어간다InputFileSystemDirectory
그리고OutputFileSystemDirectory
: 입력 및 출력에 대한 작업 디렉토리를 정의
이러한 건설 블록을 사용하면 ASP.NET 앱은 LaTeX를 수요, 캐시 결과 및 크리스프 PNG 평등을 신뢰할 수 있습니다.