이 가이드는 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.Resolution200 ~ 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 평등을 신뢰할 수 있습니다.

More in this category