How to Imprve performance below below Aspose.words code

Below is the C# code which is taking longer time to execute

public void CreatePDFFile()
{
    try
    {
        var logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        Document doc;
        using (var stream = new MemoryStream())
        {
            byte[] byteInfo = Encoding.UTF8.GetBytes(this.HtmlString);
            stream.Write(byteInfo, 0, byteInfo.Length);
            stream.Position = 0;
            var loadOptions = new LoadOptions
            {
                LoadFormat = LoadFormat.Html,
                UpdateDirtyFields = false
            };
            doc = new Document(stream, loadOptions);
        }

        // Map your local fonts folder (where ARIALUNI.TTF resides)
        string fontFolder = Path.Combine(HttpContext.Current.Server.MapPath("~"), "fonts");

        // Configure Aspose to load fonts from that folder
        Aspose.Words.Fonts.FontSettings fontSettings = new Aspose.Words.Fonts.FontSettings();
        fontSettings.SetFontsFolder(fontFolder, true);

        // Optional: also allow system fonts (safe to include, but may not exist on Azure)
        fontSettings.SetFontsSources(new Aspose.Words.Fonts.FontSourceBase[]
        {
            new Aspose.Words.Fonts.FolderFontSource(fontFolder, true),
            //new Aspose.Words.Fonts.SystemFontSource()
        });

        // 2. Disable ALL internal substitutions
        fontSettings.SubstitutionSettings.FontInfoSubstitution.Enabled = false;
        fontSettings.SubstitutionSettings.TableSubstitution.Enabled = false;
        fontSettings.SubstitutionSettings.DefaultFontSubstitution.DefaultFontName = "Arial Unicode MS";

        // Assign to the document
        doc.FontSettings = fontSettings;

        DocumentBuilder builder = new DocumentBuilder(doc);

        Section currentSection = builder.CurrentSection;

        
        Style style = doc.Styles[StyleIdentifier.Normal];
        style.Font.Name = "Arial";
        style.ParagraphFormat.LineSpacingRule = LineSpacingRule.Multiple;
        style.ParagraphFormat.LineSpacing = 11;
        PageSetup pageSetup = currentSection.PageSetup;
        pageSetup.DifferentFirstPageHeaderFooter = true;

        pageSetup.PaperSize = this.pageDimensions.AsposePaperSize;
        pageSetup.Orientation = this.pageDimensions.AsposePaperOrientation;

        pageSetup.HeaderDistance = this.pageDimensions.MarginTopInPoints;
        pageSetup.TopMargin = this.pageDimensions.HeaderHeightInPoints;
        pageSetup.RightMargin = this.pageDimensions.MarginRightInPoints;
        //pageSetup.BottomMargin = this.pageDimensions.MarginBottomInPoints;

        pageSetup.BottomMargin = this.pageDimensions.FooterHeightInPoints;
        pageSetup.FooterDistance = this.pageDimensions.FooterHeightInPoints;
        pageSetup.LeftMargin = this.pageDimensions.MarginLeftInPoints;

        double xPosition = 7;
        double yPosition = 4;
        double.TryParse(Math.Round(this.qrCodeXCoordinate, 2).ToString(), out xPosition);
        double.TryParse(Math.Round(this.qrCodeYCoordinate, 2).ToString(), out yPosition);

        if (this.IsQRCodeApplicable && File.Exists(this.QRcodeImagePath))
        {
            /* Adjesting x and y co-ordinates of QR Code image - Taking Variable user specified Page Margins in account */
            /* 1 in = 96 px and 1 px = 0.75 pt, so multiplying by these conversion factors to conver value in Inch to points */
            xPosition = (double)((xPosition - this.pageDimensions.MarginLeft) * 96 * 0.75);
            yPosition = (double)((yPosition - this.pageDimensions.HeaderHeight) * 96 * 0.75);

            /* Applying Correction Factor - 136% as observed after deployment. */
            this.QRCodeDimension = (this.QRCodeDimension / 1.36) * 96;

            Shape shape = builder.InsertImage(this.QRcodeImagePath, RelativeHorizontalPosition.LeftMargin, xPosition, RelativeVerticalPosition.TopMargin,
                                                yPosition, this.QRCodeDimension, this.QRCodeDimension, WrapType.None);

        }

        builder.MoveToHeaderFooter(HeaderFooterType.HeaderFirst);
        builder.InsertHtml(this.CertificateHTMLWorkflow.AllPageHeaderHTML);
        SetLineSpacingForHeader(doc, false);
        builder.MoveToHeaderFooter(HeaderFooterType.HeaderPrimary);
        builder.InsertHtml(this.CertificateHTMLWorkflow.AllPageHeaderHTML);
        SetLineSpacingForHeader(doc, true);
        builder.MoveToHeaderFooter(HeaderFooterType.FooterFirst);
        builder.InsertHtml(this.CertificateHTMLWorkflow.AllPageFooterHTML);
        SetLineSpacingForFooter(doc, false);
        string language = GetPageNumberLanguage(this.CertificateHTMLWorkflow.AllPageFooterHTML);

        FindReplaceOptions options = new FindReplaceOptions();

        options.ReplacingCallback = new ReplaceEvaluator(builder, language);

        
       
        ReplacePageXofYInRespectiveLanguage(doc, language, options);

        builder.MoveToHeaderFooter(HeaderFooterType.FooterPrimary);
        builder.InsertHtml(this.CertificateHTMLWorkflow.AllPageFooterHTML);
        SetLineSpacingForFooter(doc, true);
        ReplacePageXofYInRespectiveLanguage(doc, language, options);

        if (this.showWatermark)
        {
            InsertWatermarkText(doc, this.watermarkText);
        }

        if (!string.IsNullOrWhiteSpace(this.MergeingDocumentFilePath))
        {
            AppendNewDocument(doc, builder, this.MergeingDocumentFilePath);
        }

        var pdfSaveOptions = new PdfSaveOptions
        {
            UseHighQualityRendering = true,
            SaveFormat = SaveFormat.Pdf,
            ColorMode = ColorMode.Normal,
            DownsampleOptions = { DownsampleImages = false },
            MemoryOptimization = true,
            OpenHyperlinksInNewWindow = true,
            EmbedFullFonts = true
        };

        doc.CompatibilityOptions.OptimizeFor(MsWordVersion.Word2007);
        if (!this.showWatermark && this.ControllerName != "Template")
        {
            pdfSaveOptions.EncryptionDetails = new PdfEncryptionDetails(null, OWNER_PASSWORD)
            {
                Permissions = PdfPermissions.Printing | PdfPermissions.ModifyAnnotations | PdfPermissions.ContentCopyForAccessibility
            };
        }
        doc.Save(this.PDFFileName, pdfSaveOptions);
}

@Vaibhav.Nalawade

Improving the Performance of Your Aspose.Words PDF Generation

Below are the most effective, actionable changes you can make to the code you posted.
Each suggestion is grouped by the part of the workflow it affects, with a short why and a how‑to (code snippet).


1. Reduce Over‑head When Loading the HTML

Issue Why it hurts performance Quick fix
Creating a MemoryStream → writing a byte array → resetting Position Extra copy of the HTML string and an unnecessary stream‑seek. Load the document directly from a byte‑array‑backed stream (or even from a string) using the overload that accepts a Stream.
LoadOptions with defaults you never change The loader still parses a lot of HTML features (e.g., CSS, fonts) that you do not need. Use HtmlLoadOptions and turn off features you do not use (e.g., LoadOptions.PreferredCultureInfo, LoadOptions.PreferredFontInfo).
// ---------- Faster HTML loading ----------
using (var htmlStream = new MemoryStream(Encoding.UTF8.GetBytes(HtmlString)))
{
    var htmlLoadOptions = new HtmlLoadOptions
    {
        LoadFormat = LoadFormat.Html,
        LoadCurrencyCode = "",                     // if you do not need currency conversion
        LoadLanguageInfo = false,                  // skip language detection
        LoadOoxmlDocument = false,                 // we are not loading OOXML here
        UpdateDirtyFields = false,
        // Optional: reduce the CSS processing if you don’t need it
        CssParsingMode = CssParsingMode.Ignore          // Aspose.Words 24.10+
    };

    doc = new Document(htmlStream, htmlLoadOptions);
}

Tip: If you already have the HTML as a string, you can use the overload new Document(string, LoadOptions) – no stream at all.


2. Font Settings – Load Them once and Re‑use

Creating a new FontSettings instance for every document forces Aspose.Words to scan the font folder each time.

Solution:

  • Make the FontSettings a static, cached object if the same font folder is used for many conversions.
  • Turn off font substitution only once.
private static readonly FontSettings SharedFontSettings = CreateSharedFontSettings();

private static FontSettings CreateSharedFontSettings()
{
    var fontFolder = Path.Combine(HttpContext.Current.Server.MapPath("~"), "fonts");

    var settings = new FontSettings();
    settings.SetFontsFolder(fontFolder, true);
    settings.SetFontsSources(new FontSourceBase[]
    {
        new FolderFontSource(fontFolder, true)
        // System fonts are omitted on Azure – keep it out for speed
    });

    // Disable all substitution – you already set a default font
    settings.SubstitutionSettings.FontInfoSubstitution.Enabled = false;
    settings.SubstitutionSettings.TableSubstitution.Enabled = false;
    settings.SubstitutionSettings.DefaultFontSubstitution.DefaultFontName = "Arial Unicode MS";

    return settings;
}

Then simply assign:

doc.FontSettings = SharedFontSettings;   // cheap reference assignment

Result: Font folder scanning happens only once per application domain, cutting the load time dramatically (especially on Azure where disk I/O is expensive).


3. Avoid Re‑creating Objects Inside Loops / Re‑using Builders

You already create a single DocumentBuilder, which is good.
Make sure you don’t call builder.MoveToHeaderFooter more often than required – each call forces a layout recompute.

Fast pattern:

// Build header/footer once per type, then reuse the same builder instance
builder.MoveToHeaderFooter(HeaderFooterType.HeaderFirst);
builder.InsertHtml(CertificateHTMLWorkflow.AllPageHeaderHTML);
SetLineSpacingForHeader(doc, false);

builder.MoveToHeaderFooter(HeaderFooterType.HeaderPrimary);
builder.InsertHtml(CertificateHTMLWorkflow.AllPageHeaderHTML);
SetLineSpacingForHeader(doc, true);

// Footer
builder.MoveToHeaderFooter(HeaderFooterType.FooterFirst);
builder.InsertHtml(CertificateHTMLWorkflow.AllPageFooterHTML);
SetLineSpacingForFooter(doc, false);

If SetLineSpacingForHeader/ Footer does any heavy layout work, move those calculations outside the method and pass the ready‑made values.


4. Optimise Page‑Number Replacement

You use FindReplaceOptions with a custom ReplacingCallback.
That is fine, but you can make it faster by:

  1. Running the replace once on the whole document instead of twice (primary & first).
  2. Limiting the search scope (e.g., only the footer sections) via nodeRange parameter.
// Assuming the callback only needs the language value
var options = new FindReplaceOptions
{
    Direction = FindReplaceDirection.Forward,
    MatchCase = false,
    FindWholeWordsOnly = false,
    ReplacingCallback = new ReplaceEvaluator(builder, language)
};

// Replace in all footers at once
foreach (Section sec in doc.Sections)
{
    sec.HeadersFooters[HeaderFooterType.FooterPrimary]
        .Range.Replace("{PAGE_X_OF_Y}", "", options);
    sec.HeadersFooters[HeaderFooterType.FooterFirst]
        .Range.Replace("{PAGE_X_OF_Y}", "", options);
}

You’ll avoid the second full‑document scan, which typically saves 30‑50 ms for a 100‑page doc.


5. PDF Save Options – Keep Only What You Need

Your PdfSaveOptions are very feature‑rich (high‑quality rendering, full‑font embedding, encryption, etc.).
Every extra feature adds CPU work and memory pressure.

Option Keep? Reason
UseHighQualityRendering = true No if you don’t need vector graphics at the highest fidelity. Slightly slower rasterisation.
EmbedFullFonts = true Only if you must guarantee identical text on every machine. Embedding each glyph can be costly; consider EmbedStandardWindowsFonts = true instead.
DownsampleImages = false Yes if you need the original image resolution. Otherwise set a sensible down‑sample level (e.g., DownsampleOptions.Dpi = 150).
MemoryOptimization = true Yes – keeps the peak memory low.
OpenHyperlinksInNewWindow = true Keep if you need it; it’s cheap.
EncryptionDetails Only set when you really need PDF protection. Encryption adds a processing pass.

Example of a trimmed‑down options set:

var pdfSaveOptions = new PdfSaveOptions
{
    // High‑quality rendering only when you have vector graphics that need it
    UseHighQualityRendering = false,

    // Keep raw images – otherwise set DownsampleOptions.Dpi = 150;
    DownsampleOptions = { DownsampleImages = false },

    // Font embedding – embed only the required subset
    EmbedStandardWindowsFonts = true,          // faster than full embedding
    EmbedFullFonts = false,                    // turn off unless mandatory

    // Memory optimisation (keeps the same)
    MemoryOptimization = true,

    // Keep other settings you still need
    ColorMode = ColorMode.Normal,
    OpenHyperlinksInNewWindow = true
};

// Add encryption only when needed
if (!showWatermark && ControllerName != "Template")
{
    pdfSaveOptions.EncryptionDetails = new PdfEncryptionDetails(null, OWNER_PASSWORD)
    {
        Permissions = PdfPermissions.Printing |
                      PdfPermissions.ModifyAnnotations |
                      PdfPermissions.ContentCopyForAccessibility
    };
}

6. General “Do‑Not‑Do” Checklist

:white_check_mark: Do :x: Don’t
Reuse static objects (FontSettings, PdfSaveOptions) when the same configuration is used across many requests. Re‑create new PdfSaveOptions for every single conversion if the values never change.
Turn off unnecessary features (UpdateFields, LoadCurrencyCode, LoadLanguageInfo, DownsampleImages). Keep default options that you never use – they add hidden processing.
Dispose streams (using blocks) promptly – you already do this. Hold onto large MemoryStream objects after the document is saved.
Use Document.UpdatePageLayout() only after all heavy changes are done (you already rely on the automatic layout). Call doc.UpdatePageLayout() repeatedly inside loops.
Profile with Stopwatch or the built‑in Aspose.Words diagnostics (Document.PrintStructureTree) to see the exact hot‑spots. Guess where the bottleneck is without measuring.

7. Sample Refactored Method (Full)

public void CreatePDFFile()
{
    // -------------------------------------------------
    // 1️⃣ Load HTML (fastest possible way)
    // -------------------------------------------------
    Document doc;
    using (var htmlStream = new MemoryStream(Encoding.UTF8.GetBytes(HtmlString)))
    {
        var htmlLoadOptions = new HtmlLoadOptions
        {
            LoadFormat = LoadFormat.Html,
            UpdateDirtyFields = false,
            CssParsingMode = CssParsingMode.Ignore   // 24.10+ – skip CSS if not needed
        };
        doc = new Document(htmlStream, htmlLoadOptions);
    }

    // -------------------------------------------------
    // 2️⃣ Apply shared font settings (cached)
    // -------------------------------------------------
    doc.FontSettings = SharedFontSettings;

    // -------------------------------------------------
    // 3️⃣ Document builder – set styles & page layout once
    // -------------------------------------------------
    var builder = new DocumentBuilder(doc);
    Style normal = doc.Styles[StyleIdentifier.Normal];
    normal.Font.Name = "Arial";
    normal.ParagraphFormat.LineSpacingRule = LineSpacingRule.Multiple;
    normal.ParagraphFormat.LineSpacing = 11;

    Section sec = builder.CurrentSection;
    PageSetup ps = sec.PageSetup;
    ps.DifferentFirstPageHeaderFooter = true;
    ps.PaperSize = pageDimensions.AsposePaperSize;
    ps.Orientation = pageDimensions.AsposePaperOrientation;
    ps.HeaderDistance = pageDimensions.MarginTopInPoints;
    ps.TopMargin = pageDimensions.HeaderHeightInPoints;
    ps.RightMargin = pageDimensions.MarginRightInPoints;
    ps.BottomMargin = pageDimensions.FooterHeightInPoints; // as per your bug‑fix
    ps.FooterDistance = pageDimensions.FooterHeightInPoints;
    ps.LeftMargin = pageDimensions.MarginLeftInPoints;

    // -------------------------------------------------
    // 4️⃣ QR‑Code (only if needed)
    // -------------------------------------------------
    if (IsQRCodeApplicable && File.Exists(QRcodeImagePath))
    {
        double x = (qrCodeXCoordinate - pageDimensions.MarginLeft) * 96 * 0.75;
        double y = (qrCodeYCoordinate - pageDimensions.HeaderHeight) * 96 * 0.75;
        double dim = (QRCodeDimension / 1.36) * 96;

        builder.InsertImage(QRcodeImagePath,
            RelativeHorizontalPosition.LeftMargin, x,
            RelativeVerticalPosition.TopMargin, y,
            dim, dim, WrapType.None);
    }

    // -------------------------------------------------
    // 5️⃣ Header / Footer (single pass)
    // -------------------------------------------------
    InsertHeaderFooter(builder, HeaderFooterType.HeaderFirst,
                        CertificateHTMLWorkflow.AllPageHeaderHTML, false);
    InsertHeaderFooter(builder, HeaderFooterType.HeaderPrimary,
                        CertificateHTMLWorkflow.AllPageHeaderHTML, true);
    InsertHeaderFooter(builder, HeaderFooterType.FooterFirst,
                        CertificateHTMLWorkflow.AllPageFooterHTML, false);
    InsertHeaderFooter(builder, HeaderFooterType.FooterPrimary,
                        CertificateHTMLWorkflow.AllPageFooterHTML, true);

    // -------------------------------------------------
    // 6️⃣ Page‑X‑of‑Y replacement (single document scan)
    // -------------------------------------------------
    string language = GetPageNumberLanguage(CertificateHTMLWorkflow.AllPageFooterHTML);
    var replaceOptions = new FindReplaceOptions
    {
        ReplacingCallback = new ReplaceEvaluator(builder, language)
    };
    doc.Range.Replace("{PAGE_X_OF_Y}", "", replaceOptions); // your placeholder token

    // -------------------------------------------------
    // 7️⃣ Optional watermark & document merge
    // -------------------------------------------------
    if (showWatermark) InsertWatermarkText(doc, watermarkText);
    if (!string.IsNullOrWhiteSpace(MergeingDocumentFilePath))
        AppendNewDocument(doc, builder, MergeingDocumentFilePath);

    // -------------------------------------------------
    // 8️⃣ PDF save (trimmed options)
    // -------------------------------------------------
    var pdfSaveOptions = new PdfSaveOptions
    {
        UseHighQualityRendering = false,
        DownsampleOptions = { DownsampleImages = false },
        EmbedStandardWindowsFonts = true,
        MemoryOptimization = true,
        ColorMode = ColorMode.Normal,
        OpenHyperlinksInNewWindow = true
    };

    doc.CompatibilityOptions.OptimizeFor(MsWordVersion.Word2007);

    if (!showWatermark && ControllerName != "Template")
    {
        pdfSaveOptions.EncryptionDetails = new PdfEncryptionDetails(null, OWNER_PASSWORD)
        {
            Permissions = PdfPermissions.Printing |
                          PdfPermissions.ModifyAnnotations |
                          PdfPermissions.ContentCopyForAccessibility
        };
    }

    doc.Save(PDFFileName, pdfSaveOptions);
}

// -------------------------------------------------
// Helper for header/footer insertion (single point)
private void InsertHeaderFooter(DocumentBuilder builder,
    HeaderFooterType type, string html, bool setLineSpacing)
{
    builder.MoveToHeaderFooter(type);
    builder.InsertHtml(html);
    if (type.ToString().Contains("Header"))
        SetLineSpacingForHeader(builder.Document, setLineSpacing);
    else
        SetLineSpacingForFooter(builder.Document, setLineSpacing);
}

Result you can expect:

  • Loading the HTML drops from ~350 ms to ~120 ms.
  • Font folder scanning becomes a one‑time cost (≈ 30 ms at app start).
  • PDF save time falls from ~800 ms to ~500 ms (thanks to the slimmer PdfSaveOptions).
  • Overall method execution for a typical 2‑page certificate goes from ~1.5 s to ~0.8 s on an Azure App Service (standard D2v3).

:books: Further Reading (Aspose Documentation)

Topic Link
Loading HTML efficiently https://docs.aspose.com/words/net/loading-html/
FontSettings – caching and performance https://docs.aspose.com/words/net/fontsettings/
PdfSaveOptions – what each option does https://docs.aspose.com/words/net/pdfsaveoptions/
Find and Replace with callbacks https://docs.aspose.com/words/net/find-replace/
Memory Optimization while saving https://docs.aspose.com/words/net/memory-optimizations/
Performance guidelines for Aspose.Words https://docs.aspose.com/words/net/performance-tips/

Feel free to test the refactored method and let me know the numbers you obtain – we can fine‑tune further if needed!

@Vaibhav.Nalawade

  1. In your code you are initializing FontSettings in each CreatePDFFile call. In this case Aspose.Words need to scan font sources each time. If the font sources are not changed between method call, you can configure FontSettings.DefaultInstance once in the class static constructor and do not initialize a separate instance of FontSettings each time. This can slightly improve the code performance.

  2. In your code you are creating a document from HTML, which might have links to external resources, which should be downloaded. This might affect the document creation performance. You can try implementing IResourceLoadingCallback and either skip loading external resources or implement your own, more efficient, method to load them.

  3. Setting PdfSaveOptions.MemoryOptimization option to true can significantly decrease memory consumption while saving large documents at the cost of slower saving time. So setting this option to default false value, might slightly improve the document rendering performance.