Цей посібник показує, як додати реальний час LaTeX математичний рендеринг до програми ASP.NET за допомогою Aspose.TEX для .NET. Ви будете створювати невелику веб-API, яка приймає вхід LaTex, відправляє його до PNG, повертає байти зображення з правильним типом контенту, і кеш результатів на диску.

Що буде побудовано

  • Створення ASP.NET Core з:

  • Endpoint POST /api/latex/png що приймає LaTeX і повертає PNG зображення

  • Простий HTML-сторінка, де користувачі вводять рівняння і бачать прямий перегляд

  • Диск-кашинг, закріплений контент-хашем і DPI

  • Базові вхідні валідації та сандбоксні робочі каталоги

Ви можете скопіювати код і виконувати його так, як є.

Передумови

  • Windows, Linux або macOS з .NET 6 або пізніше

  • Visual Studio 2022 або VS Code* з розширенням C#

  • Пакет NuGet Aspose.TeX

dotnet add package 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 Render

Цей сервіс пише 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 Core кінцева точка

Це визначає 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. попередні оновлення з сервер-сайт PNG-вихід.

Конфігурація та розповсюдження записів

    • Ліцензія Aspose.TeX*Якщо у вас є ліцензійний файл, встановіть його під час стартапу, щоб видалити обмеження оцінки.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
    • Місце розташування *Рендер записує кеш-файли під tex-cache/ В корі контенту. на Linux або контейнерних розміщеннях ви можете встановити цей шлях на постійний обсяг. Чистити його на графіку, якщо це необхідно.
  • ** Заявка розмірів обмежень**Прикладна капсула вимагає розміру 256 КБ. Збільшується, якщо ви підтримуєте більші входи.

    • Доступ до транскордонного походження*Якщо ви обслуговуєте API з іншого походження, ніж сайт, увімкніть відповідно CORS.

Перевірка безпеки

  • Сервіс відкидає потенційно небезпечні накази, такі як \input, \include, і \write18Дотримуйте ліст дозволу і тримайте преамбюл фіксованим BuildStandaloneDocument.
  • Обмеження вхідної довжини для блокування патологічних платежів.
  • Вхід в унікальний робочий каталог за запитом і видалення каталогу після успіху.
  • Розглянемо обмеження швидкості на зворотному рівні проксі або порталу API для публічних сайтів.

Виконання типів

  • Використовуйте диск кеш, щоб уникнути рекомпутування тих самих рівнянь. зразка хашетує LaTeX плюс DPI для дедуплікації результатів.
  • Зберігайте DPI між 150 і 300 для більшості вимог до інтерфейсу. Вищий DPI збільшує час і розмір зображення.
  • Нагрійте додаток, зробивши загальну формулу на стартапі, якщо ви хочете, щоб перша запит користувача була негайною.
  • Якщо вам потрібен вихід вектора для зоомного контенту, перейдіть до SvgSaveOptions і SvgDevice, а потім включити SVG. решта трубопроводу однакова.

Troubleshooting

  • ** Блакитний вихід або помилки**Перевірте записи сервера. якщо LaTeX використовує пакети за межами фіксованого преамбулу, видаліть їх. \usepackage Вхід користувача за дизайном.
    • Кліпінг або великі маржі*Того ж standalone класу документів, як правило, відправляє маргини тісно. якщо ви все ще бачите додатковий простір, перемішайте з \[ і \] для показу математики або видаляти їх для розміру в лінії.
    • Завантажити текст *Збільшення PngSaveOptions.ResolutionПри 200 до 300 ДПІ більшість випадків UI виглядають криптовалютно.

API швидке посилання, що використовується в коді

  • TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX): створює варіанти для двигуна Object LaTeX
  • PngSaveOptions: контролює вихід PNG з Resolution і DeviceWritesImages
  • ImageDevice: буфери PNG виникають в пам’яті, коли DeviceWritesImages = false
  • TeXJob(texPath, device, options).Run()Створення Compilate the .tex Файли в пристрій
  • InputFileSystemDirectory і OutputFileSystemDirectoryВизначити робочі каталоги для входу та виходу

За допомогою цих будівельних блоків ваша програма ASP.NET може надіслати LaTeX на попит, результати кеші та надійно обслуговувати криптовалютні рівняння PNG.

More in this category