A viewer can only draw a font it has been given. If a font is neither embedded in the document nor installed where the document was converted, the renderer substitutes something else — the text still appears, so nothing looks broken, but weights, widths and line breaks all shift.
Supply the fonts explicitly and the document renders as authored:
const customFonts = ["AlfaSlabOne-Regular.ttf"].map(
(file) =>
new NutrientViewer.Font({
name: file, // the FILE's name, not the family name
callback: () => fetch(`/fonts/${file}`).then((r) => r.blob()),
}),
);
NutrientViewer.load({ container, document, customFonts });Two things catch people out. Font({ name }) wants the font file's name, with its extension — pass the family name ("Alfa Slab One") and the SDK rejects it and silently substitutes. And the configuration is snapshotted per page, not per viewer, so two viewers on one page cannot be given different customFonts; that is why the comparison below runs each pane in its own iframe.
The font list here comes from a server-side SDK, not from the viewer — how the font list is determined explains why the Web SDK cannot produce it.
The list on the left does not come from the Web SDK. It comes from a Nutrient .NET SDK endpoint that opens the document server-side and reports every font it references, along with whether the font travels with the file.
That split is deliberate, because the Web SDK has no API that enumerates a document's fonts. It is a renderer: it resolves each font as it draws, and substitutes whatever it cannot resolve. Three font-related APIs exist and none of them answers "what does this document ask for?":
contentEditingFontMatcherfires when the SDK picks a substitute during content editing. ItsFontInfocarries the font name as declared in the document — the closest the Web SDK gets — but reactively, one match at a time, and only while content editing. Never as a list, and never before you render.- The content editor's
getAvailableFonts()returns the fonts available to the editor, which is the opposite question. TextAnnotationFontsdescribes the annotation font picker, not the document.
So if you need to know what a document requires before you render it — to fetch the right fonts, or to warn that a file will not render faithfully — that answer has to come from a server-side SDK. The viewer can tell you what it substituted; it cannot tell you what was asked for.
NutrientViewer.load({
container,
document,
contentEditingFontMatcher: (match, fontInfo, availableFonts) => {
// fontInfo.name -> "AlfaSlabOne-Regular" (as declared)
// fontInfo.subsetInfo -> { originalName: "ABCDEF+AlfaSlabOne-Regular",
// demangledName: "AlfaSlabOne-Regular" }
console.log("substituting", fontInfo.name, "->", match);
return undefined; // accept the SDK's choice
},
});Worth noting: subsetInfo.demangledName strips the six-letter subset prefix a PDF adds to embedded fonts. This sample does the same normalisation when matching an inventory name to a font file, so the two ends agree.
Font API reference · Configuration (customFonts, fontSubstitutions) · Web SDK guides · .NET SDK guides