Hướng dẫn này cho thấy làm thế nào để thêm tập trình toán LaTeX thời gian thực vào một ứng dụng ASP.NET bằng cách sử dụng Aspose.TEX cho .NET. Bạn sẽ xây dựng một API web nhỏ mà chấp nhận nhập LaTex, chuyển đổi nó sang PNG, trả về các byte hình ảnh với loại nội dung chính xác, và cache kết quả trên đĩa.

Những gì bạn sẽ xây dựng

  • Một ứng dụng ASP.NET Core với:

  • Endpoint POST /api/latex/png chấp nhận LaTeX và trả lại hình ảnh PNG

  • Trang HTML đơn giản nơi người dùng nhập một sự đồng bằng và xem một bản xem trước trực tiếp

  • Disk caching chìa khóa bằng hash nội dung và DPI

  • Chứng nhận đầu vào cơ bản và thư mục làm việc sandboxed

Bạn có thể sao chép mã và chạy nó như vậy.

Nguyên tắc

  • Windows, Linux, hoặc macOS với .NET 6 hoặc mới hơn

  • Visual Studio 2022 hoặc VS Code* với phần mở rộng C#

  • gói NuGet Aspose.TeX

dotnet add package Aspose.TeX

ASPOSE.TEX Giới thiệu TeXOptions, TeXConfig.ObjectLaTeX, PngSaveOptions, ImageDevice, TeXJob, InputFileSystemDirectory, và OutputFileSystemDirectoryBạn sẽ sử dụng chúng để chuyển LaTeX sang PNG.

Dự án Layout

Tạo một ASP.NET Core Web API dự án, sau đó thêm một dịch vụ nhẹ cộng với một trang HTML tối thiểu.

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

Dịch vụ: LaTeX to PNG renderer

Dịch vụ này viết LaTeX cho một tạm thời .tex file, chạy công việc Aspose.TeX, và trả về các byte PNG. Nó cũng thực hiện một cache đơn giản trên đĩa.

// 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: điểm kết thúc tối thiểu ASP.NET Core

Điều này xác định một hợp đồng JSON, đăng ký nhà cung cấp, và tiết lộ một POST Điểm kết thúc trả về 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();

Trang kiểm tra đơn giản

Drop this vào wwwroot/index.html để thử API trong một trình duyệt mà không có bất kỳ framework 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>

chạy dự án

dotnet run

Open http://localhost:5000 hoặc http://localhost:5173 Tùy thuộc vào hồ sơ khởi động của bạn. Nhập một sự đồng bằng và nhấp vào Render. Các bản cập nhật trước với kết quả PNG bên máy chủ.

Thiết lập và triển khai ghi chú

    • Giấy phép Aspose.TeX*Nếu bạn có một tệp giấy phép, hãy thiết lập nó trong thời gian khởi động để loại bỏ giới hạn đánh giá.
// in Program.cs, before first use
// new Aspose.TeX.License().SetLicense("Aspose.Total.lic");
    • Vị trí cache*Renderer viết tệp cache dưới tex-cache/ Trong root nội dung. trên Linux hoặc containerized deployments bạn có thể lắp đặt con đường này trên một khối lượng vĩnh viễn. làm sạch nó trên lịch nếu cần thiết.
    • Giới hạn yêu cầu*Ví dụ cáp yêu cầu kích thước là 256 KB. Tăng nếu bạn hỗ trợ nhập lớn hơn.
  • Cross-origin truy cậpNếu bạn phục vụ API từ một nguồn khác với trang web, kích hoạt CORS theo đó.

Danh sách kiểm tra an ninh

  • Dịch vụ từ chối lệnh nguy hiểm tiềm năng như \input, \include, và \write18• Giữ danh sách cho phép chặt chẽ và giữ tiền mặt ổn định trong BuildStandaloneDocument.
  • Giới hạn chiều dài nhập để chặn các khoản thanh toán bệnh lý.
  • Đăng nhập vào một thư mục làm việc độc đáo theo yêu cầu và xóa thư viện sau khi thành công. mẫu chỉ giữ PNG được cache.
  • Hãy xem xét giới hạn tốc độ ở cấp proxy hoặc cổng API ngược cho các trang web công cộng.

Hiệu suất tips

  • Sử dụng cache ổ đĩa ** để tránh recomputing các phương pháp tương tự. mẫu hash LaTeX plus DPI để deduplicate kết quả.
  • Giữ DPI giữa 150 và 300 cho hầu hết các nhu cầu UI. tăng DPI cao hơn làm cho thời gian và kích thước hình ảnh.
  • Làm nóng ứng dụng bằng cách cung cấp một công thức phổ biến tại startup nếu bạn muốn yêu cầu người dùng đầu tiên là ngay lập tức.
  • Nếu bạn cần phát hành vector cho nội dung có thể zoom, chuyển sang SvgSaveOptionsSvgDevice, sau đó nhúng SVG. phần còn lại của đường ống là tương tự.

Troubleshooting

  • Blanck output hoặc lỗiKiểm tra hồ sơ máy chủ.Nếu LaTeX sử dụng các gói bên ngoài preamble cố định, loại bỏ chúng. \usepackage trong User input by design.
  • Clipping hoặc margins lớncủa The standalone lớp tài liệu thường gửi ranh giới chặt chẽ. nếu bạn vẫn thấy không gian bổ sung, nhúng với \[\] để hiển thị toán hoặc loại bỏ chúng cho kích thước inline.
    • Bài viết được lưu trữ *tăng PngSaveOptions.ResolutionTại 200 đến 300 DPI hầu hết các trường hợp UI trông crisp.

API nhanh tham chiếu được sử dụng trong mã

  • TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX): tạo các tùy chọn cho động cơ Object LaTeX
  • PngSaveOptions: kiểm soát sản xuất PNG với ResolutionDeviceWritesImages
  • ImageDevice: buffers PNG kết quả trong bộ nhớ khi DeviceWritesImages = false
  • TeXJob(texPath, device, options).Run()• Tóm tắt The .tex file vào thiết bị
  • InputFileSystemDirectoryOutputFileSystemDirectory: xác định các thư mục làm việc cho input và output

Với các khối xây dựng này, ứng dụng ASP.NET của bạn có thể cung cấp cho LaTeX theo nhu cầu, kết quả cache và phục vụ các so sánh crisp PNG một cách đáng tin cậy.

More in this category