Skip to content

XFA: measure.pushFont is not a function — rich text containing <b> or <i> breaks layout #22023

Description

@vsmeenaravi

src/core/xfa/xhtml.js calls measure.pushFont(...) in two places:

// class B, line ~375
[$pushGlyphs](measure) {
  measure.pushFont({ weight: "bold" });
  super[$pushGlyphs](measure);
  measure.popFont();
}

// class I, line ~466
[$pushGlyphs](measure) {
  measure.pushFont({ posture: "italic" });
  super[$pushGlyphs](measure);
  measure.popFont();
}

TextMeasure in src/core/xfa/text.js defines pushData, popFont, addPara, addString and compute. There is no pushFont on it, nor on FontSelector.

So any XFA document whose rich text contains <b> or <i> throws TypeError: measure.pushFont is not a function during layout.

Why it is hard to recognise

XFAFactory._createPages catches the error and only warns, so no pages are built and getDocument rejects with:

UnknownErrorException: Cannot read properties of undefined (reading 'length')

which names neither bold text nor the missing method. A document differing from a working one by a single <b> fails with a message that points nowhere near the cause.

Reproduction

Load any dynamic XFA document (enableXfa: true) whose rich text contains a <b> or <i> element, for example an exData of contentType="text/html" holding:

<body xmlns="http://www.w3.org/1999/xhtml"><p><b>Important:</b> text</p></body>

Confirmed on master today; originally found on 4.6.82.

Suggested fix

Add to TextMeasure:

pushFont(xfaFont) {
  this.fontSelector.pushData(
    xfaFont,
    { top: NaN, bottom: NaN, left: NaN, right: NaN },
    null
  );
}

The margin has to be an object of NaNs rather than null, because FontSelector.pushData tests isNaN(margin[side]). That is exactly what XhtmlObject[$pushGlyphs] already passes on the path that works.

Activity

  1. tevinch commented on Sep 28, 2026

    @tevinch

    Thanks for identifying the missing call. I reproduced the failure in 6.3.289 with a synthetic two-page XFA form containing nested <b> / <i> tags.

    One alternative to adding TextMeasure.pushFont is to prepend the semantic styles in the B and I constructors and remove their $pushGlyphs overrides, letting the existing XhtmlObject path handle measurement:

    // B constructor, after super:
    this.style = `font-weight:bold;${this.style}`;
    // I constructor, after super:
    this.style = `font-style:italic;${this.style}`;

    Prepending keeps explicit document styles such as font-weight:normal effective. It also gives the rendered elements inline styles; restoring only the missing method left the text visually normal under the standard viewer CSS in my check.

    I put the source patch, minimal PDFs and runnable comparison here. The same 11 checks pass with the repair in Chrome and the legacy distribution in Node.js; the original passes only the plain-text and disabled-XFA controls. The checks cover both nesting orders, size/spacing/wrapping, explicit normal overrides, and editing/saving/reopening twice while preserving both pages and a non-interactive field.

    These results use synthetic files, not your original document, and do not represent the full upstream test suite. I hope the small repro and patch are useful for investigating this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions