คู่มือนี้แสดงให้เห็นถึงวิธีการเพิ่ม การจัดอันดับ Math LaTeX ในเวลาจริง ไปยังแอป ASP.NET โดยใช้ Aspose.Tex สําหรับ .NET คุณจะสร้าง API ไซต์ขนาดเล็กที่ยอมรับการเข้าสู่ระบบ LaTEX, จะส่งไปยัง PNG, ส่งคืนไทม์ภาพด้วยประเภทเนื้อหาที่ถูกต้องและ cache ผลบนไดรฟ์ The walkthrough includes exact NuGet packages, minimal working code, security guards, and performance tips.

สิ่งที่คุณจะสร้าง

  • แอป ASP.NET Core ที่มี:

  • Endpoint POST /api/latex/png ที่ยอมรับ LaTeX และส่งคืนภาพ PNG

  • หน้า HTML ง่ายที่ผู้ใช้พิมพ์การสม่ําเสมอและเห็นการคาดการณ์สด

  • Disk caching ที่คีย์โดยเนื้อหา hash และ DPI

  • การยืนยันการป้อนพื้นฐานและตารางการทํางาน Sandboxed

คุณสามารถคัดลอกรหัสและดําเนินการตามที่มันเป็น

ข้อกําหนด

  • 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 renderer

บริการนี้เขียน LaTeX ไปยัง temporary .tex ไฟล์ดําเนินการงาน Aspose.TeX และส่งคืนไบต์ PNG นอกจากนี้ยังดําเนินการ cache ที่เรียบง่ายบนดิสก์

// 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: จุดสิ้นสุดขั้นต่ํา 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 ในเบราว์เซอร์โดยไม่มี 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>

การดําเนินการโครงการ

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");
  • **ตําแหน่ง cache *renderer เขียน cache ไฟล์ภายใต้ tex-cache/ ในรากเนื้อหาบน Linux หรือการจัดตั้งคอนเทนเนอร์คุณสามารถติดตั้งเส้นทางนี้ได้ในปริมาณที่คงที่ ปล้างไว้ตามแผนที่ถ้าจําเป็น

  • ขีด จํากัด ขนาดคําขอตัวอย่างแคปที่ต้องการขนาด 256 KB เพิ่มขึ้นถ้าคุณสนับสนุนการส่งข้อมูลขนาดใหญ่

  • การเข้าถึงผ่านต้นกําเนิดหากคุณให้บริการ API จากแหล่งที่มาที่แตกต่างจากเว็บไซต์คุณจะเปิดใช้งาน CORS ตามนี้

รายการตรวจสอบความปลอดภัย

  • บริการปฏิเสธคําสั่งที่เป็นอันตรายเช่น \input, \include, และ \write18. เก็บรายการอนุญาตอย่างเคร่งครัดและรักษา Preamble ที่คงที่ BuildStandaloneDocument.
  • ความยาวป้อนที่ จํากัด เพื่อบล็อกค่าธรรมเนียมที่เป็นโรค
  • ลงทะเบียนในไดเรกทอรีทํางานที่ไม่ซ้ํากันตามคําขอและลบไดรฟ์หลังจากประสบความสําเร็จ ตัวอย่างเก็บรวบรวม PNG เท่านั้น
  • โปรดพิจารณาการ จํากัด อัตราในระดับพอร์ต proxy หรือ API สําหรับเว็บไซต์สาธารณะ

ประสิทธิภาพ tips

  • ใช้ cache ของดิสก์ ** เพื่อหลีกเลี่ยงการ recomputing equations ตัวอย่าง hashes LaTeX plus DPI เพื่อ deduplicate ผล
  • รักษา DPI ระหว่าง 150 และ 300 สําหรับความต้องการ UI ส่วนใหญ่ DPI สูงขึ้นทําให้เกิดเวลาและขนาดภาพ
  • แอปทําความร้อนโดยการจัดเตรียมสูตรทั่วไปที่เริ่มต้นถ้าคุณต้องการคําขอผู้ใช้ครั้งแรกจะทันที
  • หากคุณต้องการการส่งออก vector สําหรับเนื้อหา zoomable เปลี่ยนไปที่ SvgSaveOptions และ SvgDevice, จากนั้นใส่ SVG อื่น ๆ ของท่อเป็นเดียวกัน

Troubleshooting

  • ผลลัพธ์สีขาวหรือข้อผิดพลาดตรวจสอบบันทึกเซิร์ฟเวอร์ หาก LaTeX ใช้แพคเกจนอก Preamble ที่กําหนดไว้ลบไว้ บล็อกตัวอย่าง \usepackage ในผู้ใช้ input โดยการออกแบบ
  • คลิกหรือแร่ใหญ่อะไร standalone ประเภทเอกสารมักจะส่งเส้นด้ายอย่างเคร่งครัด หากคุณยังคงเห็นพื้นที่เพิ่มเติม \[ และ \] สําหรับการแสดงแม่พิมพ์หรือลบพวกเขาสําหรับขนาด inline
  • *ข้อความที่บันทึกไว้ *การเพิ่มขึ้น PngSaveOptions.Resolutionที่ 200 ถึง 300 DPI ส่วนใหญ่ของกรณี UI ดู crisp.

API คําอธิบายที่รวดเร็วที่ใช้ในรหัส

  • TeXOptions.ConsoleAppOptions(TeXConfig.ObjectLaTeX): สร้างตัวเลือกสําหรับมอเตอร์ Object LaTeX
  • PngSaveOptions: การควบคุมการผลิต PNG กับ Resolution และ DeviceWritesImages
  • ImageDevice: buffers PNG ผลในหน่วยความจําเมื่อ DeviceWritesImages = false
  • TeXJob(texPath, device, options).Run(): คอมเพลย์ The .tex ไฟล์ในอุปกรณ์
  • InputFileSystemDirectory และ OutputFileSystemDirectory: กําหนดไดเรกทอรีการทํางานสําหรับ input และ output

ด้วยบล็อกการก่อสร้างเหล่านี้แอป ASP.NET ของคุณสามารถนําเสนอ LaTeX ตามความต้องการผลการ cache และให้บริการเท่าไหร่ PNG crisp อย่างน่าเชื่อถือ

More in this category