# Run PdfNetStack on Azure Functions

> Convert HTML to PDF and optimize PDFs in Azure Functions on the .NET isolated worker. Fonts, memory, temporary files and cold start.

Available since 1.1.

Convert HTML to PDF and optimize PDFs in Azure Functions, on the .NET isolated worker.

## Short answer

Package the fonts your HTML uses with the app, load them once, and turn installed fonts off.
Create the options once: every invocation the worker runs shares them, concurrent ones included.
Publish ready to run.

```csharp
// Created once per instance and shared by every invocation, concurrent ones included.
internal static class Pdfs
{
    private static readonly HtmlConverterOptions Options = new()
    {
        Fonts = LoadFonts(),
        UseSystemFonts = false,   // the host has no fonts, or ones that can change
        MaxPages = 200,
    };

    public static byte[] Convert(string html) => HtmlConverter.ConvertToPdf(html, Options);

    // The fonts deployed with the function, in fonts/ next to the executable.
    private static PdfFontCollection LoadFonts()
    {
        var fonts = new PdfFontCollection();
        fonts.AddDirectory(Path.Combine(AppContext.BaseDirectory, "fonts"));
        return fonts;
    }
}
```

A complete app with an HTTP trigger for HTML and an Event Grid blob trigger for optimization is
in [samples/serverless/azure-functions](https://github.com/jams4code/pdf.netstack/tree/main/samples/serverless/azure-functions).

## Fonts

Function hosts have few fonts installed or none, and what is there can change with the image.
With `UseSystemFonts = false` the output depends only on the HTML and the fonts you packaged.
CSS families that are not packaged fall back to the standard 14 fonts. A loaded font is thread
safe and shared by concurrent conversions; variable font instances made for a conversion are
released once nothing uses them. See [Fonts and assets](https://www.jadev-corp.com/guides/html-to-pdf/fonts-and-assets/).

Without a resource loader, the converter fetches nothing from the network: put images and style
sheets inline.

## Optimizing blobs

The optimizer reads its input stream to the end and holds the document in memory. Write the
result to the temporary directory and upload it from there:

```csharp
internal static class Optimizer
{
    public static async Task<PdfOptimizationResult> OptimizeAsync(Stream input, Stream destination, CancellationToken cancellationToken)
    {
        // /tmp on AWS Lambda, the instance's local storage on Azure Functions; deleted on close.
        string temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".pdf");
        await using var output = new FileStream(temp, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None,
            1 << 16, FileOptions.Asynchronous | FileOptions.DeleteOnClose);

        var options = new PdfOptimizerOptions
        {
            Streaming = true,                                    // flat memory; about 3x the file in the temp folder
            MaxInputBytes = 600L << 20,                          // larger inputs pass through unchanged
            MaxDegreeOfParallelism = Environment.ProcessorCount, // the default; vCPUs grow with memory on Lambda
        };
        PdfOptimizationResult result = await PdfOptimizer.OptimizeAsync(input, output, options, cancellationToken);

        output.Position = 0;
        await output.CopyToAsync(destination, cancellationToken);   // or hand it to the storage SDK's upload
        return result;
    }
}
```

`Path.GetTempPath()` is the instance's local storage. It is limited and differs by plan, so cap
the input size. The library itself writes nothing to disk unless you give it a path or a stream.

## Memory and concurrency

- Flex Consumption runs instances of 512 MB, 2,048 MB or 4,096 MB, and runs several invocations
  on one instance. Size memory for the concurrent work. With `Streaming`, each document being
  optimized takes a few hundred MB whatever its size, and about three times its size in the
  temporary directory; without it, about ten times the file size in memory.
- The Consumption plan caps an instance at 1.5 GB for all its concurrent invocations.
- `maxDegreeOfParallelism` under `extensions.blobs` in `host.json` bounds concurrent blobs per
  instance; `MaxDegreeOfParallelism` in `PdfOptimizerOptions` bounds the threads of one
  document.

## Cold start

Publish ready to run (`-p:PublishReadyToRun=true` with a runtime identifier), so less code is
compiled on the first request. On a development machine, in a fresh process each time, with
`tools/measure_cold_start.py`:

| First call | Ready to run | JIT |
|---|---|---|
| HTML to PDF (`UseSystemFonts = false`) | 470 ms | 800 ms |
| Text extraction of three pages | 110 ms | 250 ms |
| A line of text in an embedded TrueType font | 330 ms | 570 ms |

The machine was busy during this run, so compare the columns rather than the absolute times.
The [AWS Lambda](https://www.jadev-corp.com/guides/aws-lambda/) guide has Native AOT figures.

## Limits

- The optimizer, `ToArray` and a full `Save` hold the whole document in memory.
- The converter does not run JavaScript.

## Related

- [AWS Lambda](https://www.jadev-corp.com/guides/aws-lambda/)
- [HTML to PDF](https://www.jadev-corp.com/guides/html-to-pdf/)
- [Memory and performance](https://www.jadev-corp.com/concepts/memory-and-performance/)
- [Compress PDF](https://www.jadev-corp.com/guides/compress-pdf/)

## Which Azure Functions plan suits PDF work?

Flex Consumption, with 2,048 MB instances for optimization. With the streaming optimizer, memory stays flat whatever the file size, and the temporary directory holds about three times each file. The Consumption plan caps an instance at 1.5 GB.

## How do I make the first request faster on Azure Functions?

Publish ready to run. On a development machine the first HTML conversion in a fresh process took about 470 ms ready to run against about 800 ms with the JIT alone.

Source: https://www.jadev-corp.com/pdfnetstack/guides/azure-functions/
