Skip to content

Commit

Permalink
Fix CID / CFF font glyphs and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BobLd committed Jan 16, 2024
1 parent 3eb1010 commit 41dbb39
Show file tree
Hide file tree
Showing 50 changed files with 454 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// as the local (per font) and global (per font set) subroutines.
/// The CharStrings are lazily evaluated.
/// </summary>
internal class Type2CharStrings
internal sealed class Type2CharStrings
{
private readonly object locker = new object();
private readonly Dictionary<string, Type2Glyph> glyphs = new Dictionary<string, Type2Glyph>();
Expand Down Expand Up @@ -45,7 +45,7 @@ public Type2Glyph Generate(string name, double defaultWidthX, double nominalWidt

if (!CharStrings.TryGetValue(name, out var sequence))
{
if (!CharStrings.TryGetValue(".notdef", out sequence))
if (!CharStrings.TryGetValue(GlyphList.NotDefined, out sequence))
{
throw new InvalidOperationException($"No charstring sequence with the name /{name} in this font.");
}
Expand Down Expand Up @@ -149,15 +149,14 @@ private static void SetWidthFromArgumentsIfPresent(Type2BuildCharContext context
}
}

public class CommandSequence
public sealed class CommandSequence
{
public IReadOnlyList<float> Values { get; }
public IReadOnlyList<CommandIdentifier> CommandIdentifiers { get; }

/// <summary>
/// The ordered list of numbers and commands for a Type 2 charstring or subroutine.
/// </summary>

public CommandSequence(IReadOnlyList<float> values, IReadOnlyList<CommandIdentifier> commandIdentifiers)
{
Values = values;
Expand Down Expand Up @@ -218,7 +217,7 @@ public CommandIdentifier(int commandIndex, bool isMultiByteCommand, byte command
/// Since Type 2 CharStrings may define their width as the first argument (as a delta from the font's nominal width X)
/// we can retrieve both details for the Type 2 glyph.
/// </summary>
internal class Type2Glyph
internal sealed class Type2Glyph
{
/// <summary>
/// The path of the glyph.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public virtual string GetNameByGlyphId(int glyphId)

public virtual string GetNameByStringId(int stringId)
{
return GlyphIdToStringIdAndName.Single(x => x.Value.stringId == stringId).Value.name;
return GlyphIdToStringIdAndName.SingleOrDefault(x => x.Value.stringId == stringId).Value.name;
}

public virtual int GetStringIdByGlyphId(int glyphId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public override string GetName(int code)
{
if (!codeToNameMap.TryGetValue(code, out var name))
{
return ".notdef";
return NotDefined;
}

return name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class CompactFontFormatFont
/// <summary>
/// The font matrix for this font.
/// </summary>
public TransformationMatrix FontMatrix => TopDictionary.FontMatrix;
public TransformationMatrix FontMatrix => TopDictionary.FontMatrix.HasValue ? TopDictionary.FontMatrix.Value : TransformationMatrix.FromValues(0.001, 0, 0, 0.001, 0, 0);

/// <summary>
/// The value of Weight from the top dictionary or <see langword="null"/>.
Expand All @@ -50,6 +50,32 @@ internal CompactFontFormatFont(CompactFontFormatTopLevelDictionary topDictionary
Encoding = fontEncoding;
}

/// <summary>
/// Get the character name. Returns <c>null</c> if cannot be processed.
/// </summary>
public string GetCharacterName(int characterCode, bool isCid)
{
if (Encoding != null)
{
return Encoding.GetName(characterCode);
}

if (Charset.IsCidCharset || isCid)
{
return Charset?.GetNameByStringId(characterCode);
}

string characterName = GlyphList.AdobeGlyphList.UnicodeCodePointToName(characterCode);

if (characterName.Equals(GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
// BobLD: Not tested
return Charset?.GetNameByStringId(characterCode);
}

return characterName;
}

/// <summary>
/// Get the bounding box for the character with the given name.
/// </summary>
Expand Down Expand Up @@ -143,9 +169,17 @@ protected virtual decimal GetNominalWidthX(string characterName)
{
return PrivateDictionary.NominalWidthX;
}

/// <summary>
/// Get the Font Matrix for the corresponding character name, if available. Return <c>null</c> if not.
/// </summary>
public virtual TransformationMatrix? GetFontMatrix(string characterName)
{
return TopDictionary.FontMatrix;
}
}

internal class CompactFontFormatCidFont : CompactFontFormatFont
internal sealed class CompactFontFormatCidFont : CompactFontFormatFont
{
public IReadOnlyList<CompactFontFormatTopLevelDictionary> FontDictionaries { get; }
public IReadOnlyList<CompactFontFormatPrivateDictionary> PrivateDictionaries { get; }
Expand Down Expand Up @@ -183,6 +217,26 @@ protected override decimal GetNominalWidthX(string characterName)
return dictionary.NominalWidthX;
}

public override TransformationMatrix? GetFontMatrix(string characterName)
{
// BobLd: It seems PdfBox just returns TopDictionary.FontMatrix
// But see https://bugs.ghostscript.com/show_bug.cgi?id=690724
// and https://github.com/veraPDF/veraPDF-library/issues/1010
// TODO - We might need to multiply both matrices together

if (TopDictionary.FontMatrix is not null)
{
return TopDictionary.FontMatrix;
}

if (!TryGetFontDictionaryForCharacter(characterName, out var dictionary))
{
return null;
}

return dictionary.FontMatrix;
}

private bool TryGetPrivateDictionaryForCharacter(string characterName, out CompactFontFormatPrivateDictionary dictionary)
{
dictionary = null;
Expand All @@ -199,5 +253,22 @@ private bool TryGetPrivateDictionaryForCharacter(string characterName, out Compa

return true;
}

private bool TryGetFontDictionaryForCharacter(string characterName, out CompactFontFormatTopLevelDictionary dictionary)
{
dictionary = null;

var glyphId = Charset.GetGlyphIdByName(characterName);

var fd = FdSelect.GetFontDictionaryIndex(glyphId);
if (fd == -1)
{
return false;
}

dictionary = FontDictionaries[fd];

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/// A Compact Font Format (CFF) font program as described in The Compact Font Format specification (Adobe Technical Note #5176).
/// A CFF font may contain multiple fonts and achieves compression by sharing details between fonts in the set.
/// </summary>
public class CompactFontFormatFontCollection
public sealed class CompactFontFormatFontCollection
{
/// <summary>
/// The decoded header table for this font.
Expand Down Expand Up @@ -65,16 +65,11 @@ public TransformationMatrix GetFirstTransformationMatrix()
/// <summary>
/// Get the name for the character with the given character code from the font.
/// </summary>
public string GetCharacterName(int characterCode)
public string GetCharacterName(int characterCode, bool isCid)
{
var font = FirstFont;
var name = FirstFont.GetCharacterName(characterCode, isCid);

if (font.Encoding != null)
{
return font.Encoding.GetName(characterCode);
}

return ".notdef";
return name ?? GlyphList.NotDefined;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
{
using System.Collections.Generic;

internal class CompactFontFormatFormat0Encoding : CompactFontFormatBuiltInEncoding
internal sealed class CompactFontFormatFormat0Encoding : CompactFontFormatBuiltInEncoding
{
public CompactFontFormatFormat0Encoding(IReadOnlyList<(int code, int sid, string str)> values,
IReadOnlyList<Supplement> supplements) : base(supplements)
{
Add(0, 0, ".notdef");
Add(0, 0, NotDefined);

foreach (var value in values)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
{
using System.Collections.Generic;

internal class CompactFontFormatFormat1Encoding : CompactFontFormatBuiltInEncoding
internal sealed class CompactFontFormatFormat1Encoding : CompactFontFormatBuiltInEncoding
{
public int NumberOfRanges { get; set; }

public CompactFontFormatFormat1Encoding(int numberOfRanges, IReadOnlyList<(int code, int sid, string str)> values, IReadOnlyList<Supplement> supplements) : base(supplements)
{
NumberOfRanges = numberOfRanges;

Add(0, 0, ".notdef");
Add(0, 0, NotDefined);

foreach (var value in values)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
using Core;

internal class CompactFontFormatTopLevelDictionary
internal sealed class CompactFontFormatTopLevelDictionary
{
public const int UnsetOffset = -1;

Expand Down Expand Up @@ -30,7 +30,7 @@ internal class CompactFontFormatTopLevelDictionary

public CompactFontFormatCharStringType CharStringType { get; set; } = CompactFontFormatCharStringType.Type2;

public TransformationMatrix FontMatrix { get; set; } = TransformationMatrix.FromValues(0.001, 0, 0, 0.001, 0, 0);
public TransformationMatrix? FontMatrix { get; set; }

public decimal StrokeWidth { get; set; }

Expand Down Expand Up @@ -79,7 +79,7 @@ public override string ToString()
}
}

internal class CidFontOperators
internal sealed class CidFontOperators
{
public RegistryOrderingSupplement Ros { get; set; }

Expand All @@ -100,7 +100,7 @@ internal class CidFontOperators
public string FontName { get; set; }
}

internal class RegistryOrderingSupplement
internal sealed class RegistryOrderingSupplement
{
public string Registry { get; set; }

Expand Down
18 changes: 11 additions & 7 deletions src/UglyToad.PdfPig.Fonts/Encodings/Encoding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
/// </summary>
public abstract class Encoding
{
/// <summary>
/// <c>.notdef</c>.
/// </summary>
protected internal const string NotDefined = ".notdef";

/// <summary>
/// Mutable code to name map.
/// </summary>
Expand All @@ -24,7 +29,7 @@ public abstract class Encoding
protected readonly Dictionary<string, int> NameToCode = new Dictionary<string, int>(250);

/// <summary>
/// Maps from names to character cocdes.
/// Maps from names to character codes.
/// </summary>
public IReadOnlyDictionary<string, int> NameToCodeMap => NameToCode;

Expand All @@ -48,25 +53,24 @@ public bool ContainsCode(int code)
{
return CodeToName.ContainsKey(code);
}

/// <summary>
/// Get the character name corresponding to the given code.
/// </summary>
public virtual string GetName(int code)
{
if (!CodeToName.TryGetValue(code, out var name))
{
return ".notdef";
return NotDefined;
}

return name;
}


/// <summary>
/// Get the character code from name
/// Get the character code from name
/// </summary>
/// <param name="name">Character name (eg. euro, ampersand, A, space)</param>
/// <param name="name">Character name (e.g. euro, ampersand, A, space)</param>
/// <returns>-1 if not found otherwise the character code</returns>
public virtual int GetCode(string name)
{
Expand All @@ -90,7 +94,7 @@ protected void Add(int code, string name)
NameToCode[name] = code;
}
}

/// <summary>
/// Get a known encoding instance with the given name.
/// </summary>
Expand Down
7 changes: 5 additions & 2 deletions src/UglyToad.PdfPig.Fonts/GlyphList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
/// <summary>
/// A list which maps PostScript glyph names to unicode values.
/// </summary>
public class GlyphList
public sealed class GlyphList
{
private const string NotDefined = ".notdef";
/// <summary>
/// <c>.notdef</c>.
/// </summary>
public const string NotDefined = ".notdef";

private readonly IReadOnlyDictionary<string, string> nameToUnicode;
private readonly IReadOnlyDictionary<string, string> unicodeToName;
Expand Down
2 changes: 1 addition & 1 deletion src/UglyToad.PdfPig.Fonts/TrueType/TrueTypeFont.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public bool TryGetBoundingBox(int characterCode, Func<int, int?> characterCodeTo
/// </summary>
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
{
path = EmptyArray<PdfSubpath>.Instance;
path = null;

if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index)
|| TableRegister.GlyphTable == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Globalization;

internal class Type1CharstringDecryptedBytes
internal sealed class Type1CharstringDecryptedBytes
{
public IReadOnlyList<byte> Bytes { get; }

Expand All @@ -18,7 +18,7 @@ public Type1CharstringDecryptedBytes(IReadOnlyList<byte> bytes, int index)
{
Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
Index = index;
Name = ".notdef";
Name = GlyphList.NotDefined;
Source = SourceType.Subroutine;
}

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 41dbb39

Please sign in to comment.