[BUG] Print in Browser — Custom large fonts (Arabic/CJK) render blank on first print after page refresh
Posted: Wed May 06, 2026 1:41 pm
Bug: Custom fonts (large @font-face base64) render blank on first "Print in Browser" after page refresh
Product: Stimulsoft Reports.Blazor
Version: 2026.2.2 (reproducible across multiple versions)
Platform: Blazor Server, ASP.NET Core
Browser: Chrome, Edge (any Chromium-based)
───────────────────────────────────────────────────────────────
Description
When using StiPrintDestination.Direct (Print Without Preview / Print in Browser), any report component that uses a custom font loaded via AllowLoadingCustomFontsToClientSide = true + StiFontCollection.AddFontBase64() renders as blank/invisible text on the first print attempt after a full page refresh.
───────────────────────────────────────────────────────────────
Steps to Reproduce
Actual: Text is blank/invisible. Closing and re-printing immediately works fine.
───────────────────────────────────────────────────────────────
Root Cause (identified)
By inspecting the embedded resource Stimulsoft.Report.Web.Viewer.Scripts.Base.Actions.js inside the DLL, the printAsHtml function contains this sequence:
addCustomFontStyles injects a @font-face CSS rule with the full base64 font data into the iframe <head>:
The browser parses base64 font data asynchronously in a background thread. For a 217KB Arabic font this takes ~20–80ms. Since print() is called on the very next line — synchronously — the font has not finished parsing when the print dialog opens. The browser falls back to a system font (or renders nothing), producing blank text.
The second print succeeds because the decoded font is already in the browser's internal font cache.
This is a standard browser behavior — the Font Loading API (document.fonts.ready) exists specifically to detect this condition.
───────────────────────────────────────────────────────────────
Suggested Fix
In printAsHtml (and similarly in printAsPopup), replace the immediate print() call with a document.fonts.ready wait:
document.fonts.ready is a Promise that resolves only when all pending @font-face resources have finished loading and parsing. For system fonts or already-cached fonts it resolves instantly, so there is zero performance impact in the normal case.
Workaround (client-side, until an official fix is released)
Patch HTMLIFrameElement.prototype.contentWindow in your main JS file before Stimulsoft initializes, to transparently wrap every iframe's print() with a fonts.ready wait:
This does not modify any Stimulsoft code and has no side effects on other iframe usage.
───────────────────────────────────────────────────────────────
Environment
Product: Stimulsoft Reports.Blazor
Version: 2026.2.2 (reproducible across multiple versions)
Platform: Blazor Server, ASP.NET Core
Browser: Chrome, Edge (any Chromium-based)
───────────────────────────────────────────────────────────────
Description
When using StiPrintDestination.Direct (Print Without Preview / Print in Browser), any report component that uses a custom font loaded via AllowLoadingCustomFontsToClientSide = true + StiFontCollection.AddFontBase64() renders as blank/invisible text on the first print attempt after a full page refresh.
- First click on Print → browser print dialog opens → affected text is blank
- Close the dialog, click Print again → everything renders correctly
- Refresh the page → problem returns on the first print
───────────────────────────────────────────────────────────────
Steps to Reproduce
- Register a large custom font (e.g. Noto Naskh Arabic, ~217KB) using StiFontCollection.AddFontBase64() on the server
- Set Options.Server.AllowLoadingCustomFontsToClientSide = true
- Set Options.Toolbar.PrintDestination = StiPrintDestination.Direct
- Design a report with at least one text component using that font
- Open the page in the browser (hard refresh, Ctrl+Shift+R)
- Click the Print button → select "Print Without Preview"
Actual: Text is blank/invisible. Closing and re-printing immediately works fine.
───────────────────────────────────────────────────────────────
Root Cause (identified)
By inspecting the embedded resource Stimulsoft.Report.Web.Viewer.Scripts.Base.Actions.js inside the DLL, the printAsHtml function contains this sequence:
Code: Select all
StiJsViewer.prototype.printAsHtml = function (text, jsObject) {
// ...
printFrame.onload = function () {
jsObject.addCustomFontStyles(
jsObject.options.customOpenTypeFonts,
printFrame.contentWindow.document.getElementsByTagName("head")[0]
);
printFrame.contentWindow.focus();
printFrame.contentWindow.print(); // ← called immediately after font injection
}
printFrame.contentWindow.document.open();
printFrame.contentWindow.document.write(text);
printFrame.contentWindow.document.close();
}
Code: Select all
var cssText = "@font-face {\n" +
"font-family: '" + font.originalFontFamily + "';\n" +
"src: url(" + font.contentForCss + ");\n }";
style.innerHTML = cssText;
head.appendChild(style);
The second print succeeds because the decoded font is already in the browser's internal font cache.
This is a standard browser behavior — the Font Loading API (document.fonts.ready) exists specifically to detect this condition.
───────────────────────────────────────────────────────────────
Suggested Fix
In printAsHtml (and similarly in printAsPopup), replace the immediate print() call with a document.fonts.ready wait:
Code: Select all
// BEFORE
printFrame.onload = function () {
jsObject.addCustomFontStyles(..., head);
printFrame.contentWindow.focus();
printFrame.contentWindow.print();
}
// AFTER
printFrame.onload = function () {
jsObject.addCustomFontStyles(..., head);
printFrame.contentWindow.document.fonts.ready.then(function () {
printFrame.contentWindow.focus();
printFrame.contentWindow.print();
});
}
───────────────────────────────────────────────────────────────MDN reference: https://developer.mozilla.org/en-US/doc ... eSet/ready
"The ready read-only property of the FontFaceSet interface returns a Promise that resolves when font loading and layout operations have completed and the document is ready to be printed."
Workaround (client-side, until an official fix is released)
Patch HTMLIFrameElement.prototype.contentWindow in your main JS file before Stimulsoft initializes, to transparently wrap every iframe's print() with a fonts.ready wait:
Code: Select all
(function () {
var iframeDesc = Object.getOwnPropertyDescriptor(
HTMLIFrameElement.prototype, 'contentWindow'
);
if (!iframeDesc || !iframeDesc.get) return;
var _origGet = iframeDesc.get;
Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
get: function () {
var win = _origGet.call(this);
if (win && !win.__fontPrintPatched) {
try {
var _origPrint = win.print.bind(win);
win.print = function () {
var ready = win.document && win.document.fonts
? win.document.fonts.ready
: Promise.resolve();
ready.then(function () { _origPrint(); });
};
win.__fontPrintPatched = true;
} catch (e) { /* cross-origin iframe — skip */ }
}
return win;
},
configurable: true,
enumerable: true
});
})();
───────────────────────────────────────────────────────────────
Environment
- Stimulsoft.Reports.Blazor: 2026.2.2
- Framework: .NET 8, Blazor Server
- Browser: Chrome 124+, Edge 124+
- Font triggering the bug: Noto Naskh Arabic Regular (217KB TTF, loaded via StiFontCollection.AddFontBase64)
- Does NOT happen with: Small Latin fonts, system fonts, or StiPrintDestination.Pdf