このガイドでは、Aspose.TeX for .NET** を使用して ASP.NET アプリに **リアルタイムのラテックス マシン レンダーを追加する方法を示します. あなたは、LateX インポートを受け入れる小さな Web API を構築し、PNG にリダイレクトされ、正しいコンテンツ タイプで画像 バイトを返す、およびディスク上の結果をキャッシュします。

何を構築するか

  • ASP.NET Core アプリは以下の通りです。

  • Endpoint POST /api/latex/png LaTeXを受け入れ、PNG画像を返します。

  • 単純なHTMLページで、ユーザーが方程式を入力し、ライブプレビューを見ることができます。

  • コンテンツハッシュとDPIによるディスクキャッシング

  • 基本入力認証およびサンドボックス作業ディレクトリ

コードをコピーして実行できます。

原則

  • Windows、Linux、または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 に転送します。

プロジェクトLayout

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();
    }
}

Web 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 をクリックします. Preview の更新プログラムは、サーバー側の PNG 出力で表示されます。

設定・配置ノート

  • Aspose.TeXライセンスライセンスファイルがある場合は、スタートアップ中に設定して評価制限を削除します。
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
  • キャッシュ位置レンダーはキャッシュファイルを下に書きます。 tex-cache/ コンテンツのルートで、Linuxまたはコンテナ化されたデロイメントでは、このコースを持続可能なボリュームに組み立てることができます。

  • 要求サイズ制限サンプルカプセルは 256 KB のサイズを要求します. より大きな入力をサポートする場合は増加します。

  • トロス・オリジナルのアクセス*サイトとは異なる起源から API を提供する場合は、それに応じて CORS が有効になります。

セキュリティチェックリスト

  • サービスは、潜在的に危険なコマンドを拒否します。 \input, \include, そして \write18. 許可リストを緊密に保ち、プレームを固定する BuildStandaloneDocument.
  • 入力の長さを制限して、病理的な負荷をブロックします。
  • リクエストによってユニークなワークディレクトリに送信し、成功した後にドライブを削除します. サンプルはキャッシュされたPNGのみを保存します。
  • 公共サイトのための逆プロキシまたはAPIゲートウェアレベルでの割合制限を検討します。

パフォーマンスタイプ

  • 同じ方程式を再編するのを避けるために ディスクキャッシュ を使用します. サンプルは LaTeX プラス DPI をハッシュして結果を複製します。
  • ほとんどのUI ニーズのために DPI 150 から 300 の間を保持します. より高い DPI は、時間と画像のサイズを増加させます。
  • 最初のユーザーリクエストが即時であることを望む場合、スタートアップで一般的な公式を提供することによってアプリを加熱します。
  • ゾーム可能なコンテンツのためのベクトル出力が必要な場合は、 SvgSaveOptions そして SvgDeviceその後、SVGを入れ、残りの管道は同じです。

Troubleshooting

  • ** ブラック出力またはエラー**サーバーのログをチェックします. LaTeX が固定プレームの外でパッケージを使用している場合は、それらを削除します。 \usepackage デザインによるユーザー入力
    • クリップまたは大きなマージン*The standalone ドキュメントクラスは通常、マージンを厳密に送ります. あなたがまだ余分なスペースを見ている場合は、 \[ そして \] 数値を表示するか、インラインサイズで削除します。
    • テキスト*増加 PngSaveOptions.Resolution200〜300 DPI では、ほとんどの UI のケースは crisp のように見えます。

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 をリクエスト、キャッシュ 結果、および crisp PNG 方程式を信頼性的に提供することができます。

More in this category