Deze gids toont hoe u real-time LaTeX mathematische rendering toevoegt aan een ASP.NET-app met behulp van Aspose.TEX voor .NET. U bouwt een kleine web API die de LaTex-input accepteert, het rendert naar PNG, de beelden byten met de juiste inhoudstype retourneert en cache resultaten op de schijf.

Wat je zal bouwen

  • Een ASP.NET Core app met:

  • Endpoint POST /api/latex/png die LaTeX accepteert en een PNG-afbeelding retourneert

  • Eenvoudige HTML-pagina waar gebruikers een vergelijking invoeren en een live voorbeeld zien

  • Disk caching met een inhoudshash en DPI

  • Basic input validatie en sandboxed werkdirecties

U kunt de code kopiëren en draaien zoals het is.

Voorwaarden

  • Windows, Linux of macOS met .NET 6 of later

  • Visual Studio 2022 of VS Code* met C#-uitbreiding

  • NuGet pakket Aspose.TeX

dotnet add package Aspose.TeX

Aspose.TeX vertonen TeXOptions, TeXConfig.ObjectLaTeX, PngSaveOptions, ImageDevice, TeXJob, InputFileSystemDirectory, en OutputFileSystemDirectoryU zult deze gebruiken om LaTeX naar PNG te geven.

Project Layout

Creëer een ASP.NET Core Web API project en voeg vervolgens een lichte service plus een minimaal HTML-pagina toe.

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

Service: LaTeX tot PNG renderer

Deze service schrijft de LaTeX naar een tijdelijke .tex bestand, loopt de Aspose.TeX werk, en geeft de PNG byten terug. Het implementeert ook een eenvoudige cache op de schijf.

// 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: minimaal ASP.NET Core eindpunt

Dit definieert een JSON-overeenkomst, registreert de renderer en legt een POST Het eindpunt dat PNG terugbrengt.

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

Eenvoudige testpagina

Drop dit in wwwroot/index.html Probeer de API in een browser zonder een front-end framework.

<!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>

Run het project

dotnet run

Open http://localhost:5000 of http://localhost:5173 Afhankelijk van uw startprofiel. Type een vergelijking en klik op Render. De voorbeeld updates met server-side PNG output.

Configuratie en implementatie noten

  • Aspose.TeX licentieAls u een licentiebestand hebt, installeer het tijdens start-up om de beoordelingsgrens te verwijderen.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
    • Locatie van de cache*De renderer schrijft cache-bestanden onder tex-cache/ In de contentroot. op Linux of containerized deployments kunt u deze route monteren op een blijvende hoeveelheid. schoon het in een schema indien nodig.
  • Vraag grootte beperkingenHet voorbeeld bevat een verzoekgrootte van 256 KB. Verhoog als u grotere inputs ondersteunt.

  • **Cross-origin toegang*Als u de API van een andere oorsprong dan de site dient, kunt u CORS op deze manier activeren.

Veiligheidschecklijst

  • De service weigert potentieel gevaarlijke bestellingen zoals \input, \include, en \write18Houd de toelatingslist dicht en houd het preamble vast in BuildStandaloneDocument.
  • Beperk de ingang lengte om pathologische lading te blokkeren.
  • Verwijder in een unieke werkdirector per verzoek en verwijder de directory na succes. het monster houdt alleen de cached PNG.
  • Overweeg tariefbeperkingen op het omgekeerde proxy- of API-gateway-niveau voor openbare sites.

Performance tips

  • Gebruik disk cache om te voorkomen dat dezelfde vergelijkingen worden gecomputeerd.De sample hashes LaTeX plus DPI om de resultaten af te dupliceren.
  • Houd DPI tussen 150 en 300 voor de meeste UI-behoeften.
  • Verwarm de app door een gemeenschappelijke formule te geven bij start-up als u wilt dat de eerste gebruikersverzoek onmiddellijk is.
  • Als u een vector-uitgang nodig hebt voor zoombare inhoud, wisselen SvgSaveOptions en SvgDevice, vervolgens de SVG in. De rest van de pijpleiding is hetzelfde.

Troubleshooting

  • Blank uitgang of foutenControleer de serverlogs.Als de LaTeX pakketten gebruikt buiten de vaste preamble, verwijder ze. \usepackage Gebruikersinvoer door ontwerp.
  • Clipping of grote margesDe standalone documentklasse stuurt meestal de marges nauw. als u nog steeds extra ruimte ziet, wrap met \[ en \] voor het weergeven van materie of verwijder ze voor inline grootte.
    • Gehaalde tekst *verhogen PngSaveOptions.ResolutionBij 200 tot 300 DPI lijken de meeste UI-gevallen crisp.

API snelle referentie gebruikt in code

  • TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX): maakt opties voor de Object LaTeX-motor
  • PngSaveOptions: controleert PNG-uitgang met Resolution en DeviceWritesImages
  • ImageDevice: buffers PNG resulteren in geheugen wanneer DeviceWritesImages = false
  • TeXJob(texPath, device, options).Run()• Compileren van de .tex bestanden in het apparaat
  • InputFileSystemDirectory en OutputFileSystemDirectory: definieert de werkdirecties voor input en output

Met deze bouwblokken kan uw ASP.NET-app LaTeX op vraag, cache-resultaten en crisp PNG-equaties betrouwbaar aanbieden.

More in this category