From cfa22c50f0dd2e4d1cb079628fa3b14b6d125f01 Mon Sep 17 00:00:00 2001 From: jeske Date: Wed, 15 Jul 2026 18:59:46 -0600 Subject: [PATCH] Expose font dictionary indirect reference on FontDetails Documents can embed multiple font objects sharing one BaseFont name (e.g. two different subsets of a typeface embedded without unique subset prefixes). Consumers resolving fonts by Letter.FontName alone cannot tell them apart, causing glyph-level rendering corruption when the subsets have complementary glyph coverage. - FontDetails.FontDictionaryReference: the indirect reference of the font dictionary the font was loaded from (null for direct/synthetic fonts); propagated through AsBold()/WithName() - ResourceStore stamps the reference at both font-load sites (LoadFontDictionary and GetFontDirectly) This gives Letter.FontDetails a stable in-document font identity without changing the IFont interface or any rendering behavior. --- src/UglyToad.PdfPig/Content/ResourceStore.cs | 9 +++- src/UglyToad.PdfPig/PdfFonts/FontDetails.cs | 43 +++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/UglyToad.PdfPig/Content/ResourceStore.cs b/src/UglyToad.PdfPig/Content/ResourceStore.cs index a65347ec2..be75fa0be 100644 --- a/src/UglyToad.PdfPig/Content/ResourceStore.cs +++ b/src/UglyToad.PdfPig/Content/ResourceStore.cs @@ -232,7 +232,12 @@ private void LoadFontDictionary(DictionaryToken fontDictionary) try { - loadedFonts[reference] = fontFactory.Get(fontObject); + var loadedFont = fontFactory.Get(fontObject); + // Stamp the font dictionary's indirect reference so consumers can + // distinguish same-named fonts (e.g. two subsets of one typeface + // embedded without unique subset prefixes). See FontDetails.FontDictionaryReference. + loadedFont.Details?.SetFontDictionaryReference(reference); + loadedFonts[reference] = loadedFont; } catch { @@ -286,6 +291,8 @@ public IFont GetFontDirectly(IndirectReferenceToken fontReferenceToken) var font = fontFactory.Get(fontDictionaryToken); + font.Details?.SetFontDictionaryReference(fontReferenceToken.Data); + return font; } diff --git a/src/UglyToad.PdfPig/PdfFonts/FontDetails.cs b/src/UglyToad.PdfPig/PdfFonts/FontDetails.cs index bd6066719..1a289e481 100644 --- a/src/UglyToad.PdfPig/PdfFonts/FontDetails.cs +++ b/src/UglyToad.PdfPig/PdfFonts/FontDetails.cs @@ -1,5 +1,7 @@ namespace UglyToad.PdfPig.PdfFonts { + using Core; + /// /// Summary details of the font used to draw a glyph. /// @@ -35,6 +37,16 @@ public sealed class FontDetails /// public bool IsItalic { get; } + /// + /// The indirect reference of the font dictionary this font was loaded from, + /// when it was loaded from an indirect object. Uniquely identifies the font + /// within its source document even when multiple font objects share the same + /// name (e.g. two different subsets of one typeface embedded without unique + /// subset prefixes). null for fonts defined directly inside a resource + /// dictionary (no indirect object) or for synthetic/fallback fonts. + /// + public IndirectReference? FontDictionaryReference { get; private set; } + private readonly Lazy _bold; /// @@ -47,7 +59,14 @@ public FontDetails(string? name, bool isBold, int weight, bool isItalic) Weight = weight; IsItalic = isItalic; - _bold = isBold ? new Lazy(() => this) : new Lazy(() => new FontDetails(Name, true, Weight, IsItalic)); + // Evaluated lazily so a FontDictionaryReference assigned after construction + // still propagates to the bold variant. + _bold = isBold + ? new Lazy(() => this) + : new Lazy(() => new FontDetails(Name, true, Weight, IsItalic) + { + FontDictionaryReference = FontDictionaryReference + }); } /// @@ -66,8 +85,30 @@ public FontDetails AsBold() internal FontDetails WithName(string? name) => name is not null ? new FontDetails(name, IsBold, Weight, IsItalic) + { + FontDictionaryReference = FontDictionaryReference + } : this; + /// + /// Record the indirect reference of the font dictionary this font was loaded from. + /// Called by the resource system immediately after font construction; assigning the + /// same value again is a no-op. + /// + internal void SetFontDictionaryReference(IndirectReference reference) + { + FontDictionaryReference = reference; + + // Propagate to an already-materialized bold variant. The bold Lazy caches its + // value, so a variant created before this stamp would otherwise keep a stale + // null reference forever. (A not-yet-materialized variant picks the reference + // up at evaluation time via the initializer closure.) + if (_bold.IsValueCreated && !ReferenceEquals(_bold.Value, this)) + { + _bold.Value.SetFontDictionaryReference(reference); + } + } + /// public override string ToString() {