Skip to content

#2477 - Fonts: Configurable font embedding policy (fsType / OnFontEmbedding - #2474

Open
swmal wants to merge 8 commits into
develop9from
feature/i2476
Open

#2477 - Fonts: Configurable font embedding policy (fsType / OnFontEmbedding#2474
swmal wants to merge 8 commits into
develop9from
feature/i2476

Conversation

@swmal

@swmal swmal commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Fonts: Configurable font embedding policy (fsType / OnFontEmbedding)

The PDF exporter now respects the embedding restrictions a font declares through its OS/2 fsType field, instead of always subsetting unconditionally.

fsType is interpreted into a FontEmbeddingRestriction (None, NoSubsetting, NoEmbedding) via Os2Table.GetEmbeddingRestriction(). The engine combines this with user configuration in OpenTypeFontEngine.ResolveEmbeddingDecision, applied by the document-wide subset builder (see below) before subsetting:

  • No-subsetting fonts are embedded whole rather than subsetted.
  • Restricted License fonts throw by default, so we never silently embed a font the user may not have the right to embed.
  • All other fonts are subsetted as before.

Since fsType is a vendor declaration and not a technical lock, users who hold an embedding licence can override the default via Workbook.ConfigureFonts(cfg => cfg.OnFontEmbedding(...)), returning Subset, EmbedWhole, Skip, or Default. The decision is cached per font, so the callback fires once per font.

New public types live in EPPlus.Interfaces. Adds unit tests covering interpretation, default policy, and callback override. Wiki updated, see https://github.com/EPPlusSoftware/EPPlus/wiki/Configure-fonts#embedding-restrictions

Fonts: Document-wide subsetting (DocumentFontSubsetBuilder)

Font subsetting has moved from a per-font model to a document-wide one. Previously each font subsetted independently, so when several fonts shared a fallback (an emoji font, or the Archivo Narrow last resort) each produced an incompatible glyph-id space — yielding oversized and, when fonts collided, corrupt PDFs where cells rendered each other's glyphs.

Subsetting is now owned by a single DocumentFontSubsetBuilder (builder pattern): text is collected per requested font across the whole document, then one Build() runs all checks — embedding restrictions, fallback resolution, subset construction — producing one shared subset per distinct font. FontSubsetManager is removed; SingleFontSubsetter remains as the low-level single-font building block. Skip is now a real fallback path: a skipped font leaves the chain and its code points are redistributed, with the last-resort font guaranteeing non-empty output.

On the PDF side, PdfDictionaries owns the builder and a single BuildSubsets replaces the old per-font loop. The font resource dictionary is populated only from fonts actually used after shaping, so skipped or unused fonts are no longer embedded — the root of the oversized-file bug. GetFont resolves a requested font to the one that actually renders it, so a font resolved via fallback still finds its resource. Text measurement shapes against whole fonts (advance widths are identical), avoiding redundant subsetting.

Adds tests for the skip-as-fallback path, the shared-fallback collapse (the original corruption case), and all four export paths.

Merge note: rebased onto the per-worksheet printer-settings change. Multi-sheet layout currently replicates a single PdfPageSettings across sheets (TODO(merge) in PdfCatalog.GetLayout); wiring per-sheet settings through is a coordinated follow-up. Font subsetting is document-global and unaffected.

Known limitation (out of scope): uncommon subfamilies such as "Arial Black" resolve via the fallback chain rather than matching directly, since subfamily matching only recognises Regular/Bold/Italic/BoldItalic. Tracked separately.

@swmal swmal added this to the EPPlus 9.0 or later milestone Aug 19, 2026
@swmal swmal self-assigned this Aug 19, 2026
@swmal swmal added the enhancement New feature or request label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant