3 Reasons PdfSharpCore Fields Won’t Show Up in Chrome

A few months into a recent project, my software development team hit a bug that took much longer to diagnose than it should have. We were generating PDFs server-side — filling in form fields with data from our system, saving the document, and returning it to the user. In Adobe Acrobat, the fields looked perfect, every value in the right place.

In Chrome’s built-in PDF viewer, the fields were blank.

What’s Actually Happening

PDF fields work in two layers. The first is the actual field value — the data. The second is the appearance stream — a set of pre-rendered instructions that tell PDF viewers how to visually display that value. Think of it like a field value and a cached render of that value living side-by-side.

Adobe is lenient. If the appearance stream is stale or missing, Acrobat will fall back to regenerating it from the field’s /DA (Default Appearance) string, which contains font and style information. Chrome is not. If the appearance stream doesn’t match the current value, Chrome shows nothing.

So when we set a field value programmatically and the appearance stream didn’t update to match, Chrome showed blank fields.

There were actually two separate problems contributing to this. They compounded each other in a way that made the root cause harder to find.

.Text Triggers RenderAppearance()

When using PdfSharpCore to set a text field’s value, the obvious thing to try is the .Text property:


textField.Text = "some value";

What we didn’t realize is that setting .Text internally calls RenderAppearance(). That method regenerates the appearance stream. And when it does, it wipes out the existing /DA string and any pre-existing appearance streams in the PDF template.

Our PDF templates came with carefully crafted appearance streams already baked in. Setting .Text was silently destroying them every time.

The fix is to use .Value instead, which sets the field data without triggering appearance regeneration. But you have to do a little extra work to make sure nothing important gets lost in the process.

Here’s what that looks like:


private static void SetTextFieldValue(KeyValuePair<string, string?> mapping, PdfTextField textField)
{
    // Preserve the existing /DA and /AP before we touch anything
    var originalDA = textField.Elements.GetString("/DA");
    var originalAP = textField.Elements["/AP"];

    // Set field value directly without triggering RenderAppearance
    textField.Value = new PdfString(mapping.Value ?? string.Empty);

    // Restore the Default Appearance string for font information
    if (!string.IsNullOrEmpty(originalDA))
    {
        textField.Elements.SetString("/DA", originalDA);
    }

    // Restore existing appearance streams
    if (originalAP != null)
    {
        textField.Elements["/AP"] = originalAP;
    }
}

Read the /DA and /AP before you touch the field. Set the value. Put them back. It feels like it should be unnecessary — you’re just setting a value, why would anything else change? — but with this library, it matters.

Chrome Needs the /NeedAppearances Flag

Even after switching to .Value, Chrome was still blank in some cases. This is where the second problem comes in.

The PDF spec includes a flag in a dictionary called /NeedAppearances. When it’s set to true, it tells compliant PDF viewers: “the appearance streams may not match the current field values — please re-render from the /DA string before displaying.”

Adobe does this automatically regardless of the flag. Chrome respects the flag. Without it, Chrome trusts whatever’s in the appearance stream, even if it’s stale or empty.

Setting it is straightforward. The only wrinkle is that you have to handle both the case where the key already exists in the dictionary and the case where it doesn’t:


private static void SetNeedAppearancesFlag(PdfDocument document)
{
    var acroForm = document.AcroForm;
    if (acroForm.Elements.ContainsKey("/NeedAppearances"))
    {
        acroForm.Elements["/NeedAppearances"] = new PdfBoolean(true);
    }
    else
    {
        acroForm.Elements.Add("/NeedAppearances", new PdfBoolean(true));
    }
}

This gets called once right after opening the document, before any fields are set. It’s a small thing that makes a significant difference.

Multi-Page Checkboxes Have Children

Text fields were now working. Checkboxes were a different story.

Some of our PDF templates had checkboxes that appeared on multiple pages — the same logical field rendered in multiple locations. In the PDF spec, these are represented as a parent field with a /Kids array containing child widget annotations, one per page. Setting the parent’s checked state isn’t enough. Each child widget has its own /AS (Appearance State) entry that needs to be updated independently.

/AS is a name value: /Yes for checked, /Off for unchecked. If you set the parent checkbox but leave the child widgets alone, some pages render correctly and others don’t. It’s the kind of bug that makes you feel like the PDF is haunted.


private static void SetCheckboxValue(PdfCheckBoxField checkboxField, bool isChecked, string fieldName)
{
    // Set the main checkbox field
    checkboxField.Checked = isChecked;

    // If there are child widgets (multi-page checkboxes), update each one
    if (checkboxField.Elements.ContainsKey("/Kids"))
    {
        UpdateChildWidgets(checkboxField, isChecked, fieldName);
    }
}

private static void UpdateChildWidgets(PdfCheckBoxField checkboxField, bool isChecked, string fieldName)
{
    var kidsArray = checkboxField.Elements["/Kids"] as PdfArray;
    if (kidsArray == null) return;

    var appearanceValue = isChecked ? "/Yes" : "/Off";

    for (int kidIndex = 0; kidIndex < kidsArray.Elements.Count; kidIndex++)
    {
        var kidRef = kidsArray.Elements[kidIndex] as PdfReference;
        if (kidRef?.Value is PdfDictionary kidObject)
        {
            kidObject.Elements.SetName("/AS", appearanceValue);
        }
    }

    // Set the parent appearance state too
    checkboxField.Elements.SetName("/AS", appearanceValue);
}

Putting It Together

Getting PDF fields to render correctly in Chrome came down to three things working together:

  1. Use .Value instead of .Text and manually preserve /DA and /AP
  2. Set /NeedAppearances to true on the AcroForm before touching any fields
  3. For checkboxes, update child widget appearance states in addition to the parent

None of this is obvious from the PdfSharpCore docs. The Chrome-versus-Adobe difference in particular — the fact that Chrome requires /NeedAppearances while Adobe doesn’t — isn’t something I would have guessed going in. We found it after a lot of “it works in Acrobat, why not Chrome” conversations and eventually digging into the PDF spec directly.

If you’re doing server-side PDF generation and hitting blank fields in Chrome specifically, start by checking the /NeedAppearances flag.  If fields are also rendering with the wrong font or losing formatting, the .Text vs .Value issue is likely what’s biting you.

And if you have checkboxes that only partially render on a multi-page document — go check whether your PDF has /Kids.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *