Ce guide montre comment ajouter réal-time LaTeX math rendering à une application ASP.NET en utilisant Aspose.Tex pour .NET. Vous construirez une petite API web qui accepte l’entrée LaTEX, le rend à PNG, retourne les bytes d’image avec le type de contenu correct, et cache les résultats sur le disque.

Ce que vous construirez

  • Une application ASP.NET Core avec :

  • Endpoint POST /api/latex/png qui accepte LaTeX et retourne une image PNG

  • Une page HTML simple où les utilisateurs font une équation et voient une prévision en direct

  • Le caching disque clé par un hash de contenu et DPI

  • Validation d’entrée de base et des directories de travail sandboxed

Vous pouvez copier le code et le faire comme il est.

Principaux

  • Windows, Linux ou macOS avec .NET 6 ou ultérieur

  • Visual Studio 2022 ou VS Code* avec extension C#

  • Le paquet NuGet Aspose.TeX

dotnet add package Aspose.TeX

Aspose.TeX exposé TeXOptions, TeXConfig.ObjectLaTeX, PngSaveOptions, ImageDevice, TeXJob, InputFileSystemDirectory, et OutputFileSystemDirectoryVous les utiliserez pour rendre LaTeX à PNG.

Le projet Layout

Créez un ASP.NET Core Web API, puis ajoutez un service de poids léger plus une page HTML minimale.

AsposeTexDemo/
  Program.cs
  Services/
    LatexRenderer.cs
  wwwroot/
    index.html

Service : LaTeX à PNG Render

Ce service écrira le LaTeX à une .tex fichier, exécute le travail Aspose.TeX, et retourne les bytes PNG. Il implémentera également un simple cache sur disque.

// 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 Web : Point de fin ASP.NET

Ceci définit un contrat JSON, enregistre le rendu, et expose une POST C’est un endpoint qui retourne le 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();

Page de test simple

Mettez cela dans wwwroot/index.html Essayer l’API dans un navigateur sans aucun cadre 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>

Exécutez le projet

dotnet run

Open http://localhost:5000 ou http://localhost:5173 En fonction de votre profil de lancement.Type une équation et cliquez sur Render.Les mises à jour prévisibles avec la sortie PNG du côté du serveur.

Les notes de configuration et de déploiement

  • **Aspose.TeX licence*Si vous avez un fichier de licence, définissez-le pendant le démarrage pour supprimer les limites d’évaluation.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
    • Localisation du cache*Le Render enregistre les fichiers cache tex-cache/ Dans la racine du contenu. sur Linux ou les déploiements conteneurs, vous pouvez monter cette voie sur un volume persistant.
    • Limites de taille de requête *Le tableau d’exemple demande la taille de 256 KB. Augmentez si vous soutenez des entrées plus grandes.
    • Accès à l’origine*Si vous utilisez l’API d’une origine différente du site, activez CORS en conséquence.

Sécurité Checklist

  • Le service rejette des commandes potentiellement dangereuses comme \input, \include, et \write18Gardez la liste de permission étroite et maintenez le préambule fixé BuildStandaloneDocument.
  • Limitez la longueur d’entrée pour bloquer les charges pathologiques.
  • Render dans un catalogue de travail unique par requête et supprimer le registre après succès. l’échantillon ne conserve que le PNG caché.
  • Considérons la limitation des taux au niveau du proxy ou du portail d’API inversé pour les sites publics.

Conseils de performance

  • Utilisez cache disque pour éviter de recomputer les mêmes équations.L’échantillon a hashes LaTeX plus DPI pour dédoubler les résultats.
  • Gardez DPI entre 150 et 300 pour la plupart des besoins d’UI. Une augmentation de DPI plus élevée rend le temps et la taille de l’image.
  • Réchauffez l’application en rendant une formule courante au start-up si vous voulez que la première demande d’utilisateur soit instantanée.
  • Si vous avez besoin d’une sortie vectorielle pour le contenu zoomable, cliquez sur SvgSaveOptions et SvgDevice, puis embrassez le SVG. Le reste du pipeline est le même.

Troubleshooting

  • Blanque de sortie ou erreursVérifiez les logs du serveur.Si LaTeX utilise des paquets en dehors de la préambule fixe, supprimez-les. \usepackage Input utilisateur par design.
  • Clipping ou gros margesLe standalone la classe de document envoie généralement les marges étroitement. si vous voyez encore de l’espace supplémentaire, \[ et \] pour afficher les mathématiques ou les supprimer pour la taille inline.
    • Texte écrite *Augmentation PngSaveOptions.ResolutionA 200 à 300 DPI, la plupart des cas d’UI semblent crisp.

API de référence rapide utilisé dans le code

  • TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX): crée des options pour le moteur Object LaTeX
  • PngSaveOptions: contrôler la production PNG avec Resolution et DeviceWritesImages
  • ImageDevice: les buffers PNG résultent dans la mémoire lorsque DeviceWritesImages = false
  • TeXJob(texPath, device, options).Run()Composer le .tex Fichiers dans le dispositif
  • InputFileSystemDirectory et OutputFileSystemDirectory: définir les directrices de travail pour l’entrée et la sortie

Avec ces blocs de construction, votre application ASP.NET peut rendre LaTeX sur la demande, les résultats de cache et servir les équations PNG crisp de manière fiable.

More in this category