From 016b754c5b628c3f5f125d4285a3de897fec6962 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Mon, 14 Jul 2025 15:57:01 -0500 Subject: [PATCH] back-calculate first char if last char and widths present (#1081) * back-calculate first char if last char and widths present when a truetype font has a last char and widths array in its font dictionary the first char can be calculated #644 * fix off by 1 in last char calculation --- .../Parser/Handlers/TrueTypeFontHandler.cs | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/src/UglyToad.PdfPig/PdfFonts/Parser/Handlers/TrueTypeFontHandler.cs b/src/UglyToad.PdfPig/PdfFonts/Parser/Handlers/TrueTypeFontHandler.cs index b53bb40e..f545e18b 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Handlers/TrueTypeFontHandler.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Handlers/TrueTypeFontHandler.cs @@ -49,13 +49,18 @@ public IFont Generate(DictionaryToken dictionary) { - if (!dictionary.TryGetOptionalTokenDirect(NameToken.FirstChar, pdfScanner, out NumericToken? firstCharacterToken) + int? firstChar = null; + if (!dictionary.TryGetOptionalTokenDirect(NameToken.FirstChar, + pdfScanner, + out NumericToken? firstCharacterToken) || !dictionary.TryGet(NameToken.FontDescriptor, pdfScanner, out _) - || !dictionary.TryGet(NameToken.Widths, out IToken _)) + || !dictionary.TryGet(NameToken.Widths, out _)) { + var isStandard14 = true; if (!dictionary.TryGetOptionalTokenDirect(NameToken.BaseFont, pdfScanner, out NameToken? baseFont)) { - throw new InvalidFontFormatException($"The provided TrueType font dictionary did not contain a /FirstChar or a /BaseFont entry: {dictionary}."); + throw new InvalidFontFormatException( + $"The provided TrueType font dictionary did not contain a /FirstChar or a /BaseFont entry: {dictionary}."); } // Can use the AFM descriptor despite not being Type 1! @@ -63,37 +68,53 @@ if (standard14Font is null) { - throw new InvalidFontFormatException($"The provided TrueType font dictionary did not have a /FirstChar and did not match a Standard 14 font: {dictionary}."); + if (dictionary.TryGet(NameToken.LastChar, pdfScanner, out NumericToken? lastCharToken) + && dictionary.TryGet(NameToken.Widths, pdfScanner, out ArrayToken? widthsArrayLoc)) + { + // If the widths array contains widths for characters 0 - 3 it will contain 4 entries but last char is 3. + firstChar = (lastCharToken.Int - widthsArrayLoc.Length) + 1; + isStandard14 = false; + } + else + { + throw new InvalidFontFormatException( + $"The provided TrueType font dictionary did not have a /FirstChar and did not match a Standard 14 font: {dictionary}."); + } } - var fileSystemFont = systemFontFinder.GetTrueTypeFont(baseFont.Data); - - var thisEncoding = encodingReader.Read(dictionary); - - if (thisEncoding is null) + if (isStandard14) { - thisEncoding = new AdobeFontMetricsEncoding(standard14Font); + var fileSystemFont = systemFontFinder.GetTrueTypeFont(baseFont.Data); + + var thisEncoding = encodingReader.Read(dictionary); + + if (thisEncoding is null) + { + thisEncoding = new AdobeFontMetricsEncoding(standard14Font); + } + + double[]? widthsOverride = null; + + if (dictionary.TryGet(NameToken.FirstChar, pdfScanner, out firstCharacterToken)) + { + firstChar = firstCharacterToken.Int; + } + + if (dictionary.TryGet(NameToken.Widths, pdfScanner, out ArrayToken? widthsArray)) + { + widthsOverride = widthsArray.Data.OfType() + .Select(x => x.Double).ToArray(); + } + + return new TrueTypeStandard14FallbackSimpleFont(baseFont, + standard14Font!, + thisEncoding, + fileSystemFont, + new TrueTypeStandard14FallbackSimpleFont.MetricOverrides(firstChar, widthsOverride)); } - - int? firstChar = null; - double[]? widthsOverride = null; - - if (dictionary.TryGet(NameToken.FirstChar, pdfScanner, out firstCharacterToken)) - { - firstChar = firstCharacterToken.Int; - } - - if (dictionary.TryGet(NameToken.Widths, pdfScanner, out ArrayToken? widthsArray)) - { - widthsOverride = widthsArray.Data.OfType() - .Select(x => x.Double).ToArray(); - } - - return new TrueTypeStandard14FallbackSimpleFont(baseFont, standard14Font, thisEncoding, fileSystemFont, - new TrueTypeStandard14FallbackSimpleFont.MetricOverrides(firstChar, widthsOverride)); } - var firstCharacter = firstCharacterToken.Int; + var firstCharacter = firstChar ?? firstCharacterToken!.Int; var widths = FontDictionaryAccessHelper.GetWidths(pdfScanner, dictionary);