handle missing width and height correctly for compact font format fonts #75

This commit is contained in:
Eliot Jones
2019-12-04 14:18:20 +00:00
parent 8a51795e99
commit a967e0898a
13 changed files with 264 additions and 101 deletions

View File

@@ -47,8 +47,10 @@
/// Evaluate the CharString for the character with a given name returning the path constructed for the glyph.
/// </summary>
/// <param name="name">The name of the character to retrieve the CharString for.</param>
/// <param name="defaultWidthX">The default width for the glyph from the font's private dictionary.</param>
/// <param name="nominalWidthX">The nominal width which individual glyph widths are encoded as the difference from.</param>
/// <returns>A <see cref="PdfPath"/> for the glyph.</returns>
public Type2Glyph Generate(string name)
public Type2Glyph Generate(string name, decimal defaultWidthX, decimal nominalWidthX)
{
Type2Glyph glyph;
lock (locker)
@@ -65,7 +67,7 @@
try
{
glyph = Run(sequence);
glyph = Run(sequence, defaultWidthX, nominalWidthX);
glyphs[name] = glyph;
}
@@ -78,68 +80,81 @@
return glyph;
}
private static Type2Glyph Run(CommandSequence sequence)
private static Type2Glyph Run(CommandSequence sequence, decimal defaultWidthX, decimal nominalWidthX)
{
var context = new Type2BuildCharContext();
var hasRunStackClearingCommand = false;
foreach (var command in sequence.Commands)
for (var i = 0; i < sequence.Commands.Count; i++)
{
var command = sequence.Commands[i];
var isOnlyCommand = sequence.Commands.Count == 1;
command.Match(x => context.Stack.Push(x),
x =>
{
if (!hasRunStackClearingCommand)
{
/*
x =>
{
if (!hasRunStackClearingCommand)
{
/*
* The first stack-clearing operator, which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask, hmoveto, vmoveto,
* rmoveto, or endchar, takes an additional argument — the width (as described earlier), which may be expressed as zero or one numeric argument.
*/
hasRunStackClearingCommand = true;
switch (x.Name)
{
case "hstem":
case "hstemhm":
case "vstemhm":
case "vstem":
{
var oddArgCount = context.Stack.Length % 2 != 0;
if (oddArgCount)
{
context.Width = context.Stack.PopBottom();
}
break;
}
case "hmoveto":
case "vmoveto":
SetWidthFromArgumentsIfPresent(context, 1);
break;
case "rmoveto":
SetWidthFromArgumentsIfPresent(context, 2);
break;
case "cntrmask":
case "hintmask":
case "endchar:":
SetWidthFromArgumentsIfPresent(context, 0);
break;
default:
hasRunStackClearingCommand = false;
break;
hasRunStackClearingCommand = true;
switch (x.Name)
{
case "hstem":
case "hstemhm":
case "vstemhm":
case "vstem":
{
var oddArgCount = context.Stack.Length % 2 != 0;
if (oddArgCount)
{
context.Width = nominalWidthX + context.Stack.PopBottom();
}
}
break;
}
case "hmoveto":
case "vmoveto":
SetWidthFromArgumentsIfPresent(context, nominalWidthX, 1);
break;
case "rmoveto":
SetWidthFromArgumentsIfPresent(context, nominalWidthX, 2);
break;
case "cntrmask":
case "hintmask":
SetWidthFromArgumentsIfPresent(context, nominalWidthX, 0);
break;
case "endchar":
if (isOnlyCommand)
{
context.Width = defaultWidthX;
}
else
{
SetWidthFromArgumentsIfPresent(context, nominalWidthX, 0);
}
break;
default:
hasRunStackClearingCommand = false;
break;
}
}
}
x.Run(context);
});
x.Run(context);
});
}
return new Type2Glyph(context.Path, context.Width);
}
private static void SetWidthFromArgumentsIfPresent(Type2BuildCharContext context, int expectedArgumentLength)
private static void SetWidthFromArgumentsIfPresent(Type2BuildCharContext context, decimal nomimalWidthX, int expectedArgumentLength)
{
if (context.Stack.Length > expectedArgumentLength)
{
context.Width = context.Stack.PopBottom();
context.Width = nomimalWidthX + context.Stack.PopBottom();
}
}
@@ -177,30 +192,15 @@
/// <summary>
/// The width of the glyph as a difference from the nominal width X for the font. Optional.
/// </summary>
public decimal? WidthDifferenceFromNominal { get; }
public decimal? Width { get; }
/// <summary>
/// Create a new <see cref="Type2Glyph"/>.
/// </summary>
public Type2Glyph(PdfPath path, decimal? widthDifferenceFromNominal)
public Type2Glyph(PdfPath path, decimal? width)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
WidthDifferenceFromNominal = widthDifferenceFromNominal;
}
public decimal GetWidth(CompactFontFormatPrivateDictionary privateDictionary)
{
if (privateDictionary == null)
{
throw new ArgumentNullException(nameof(privateDictionary));
}
if (!WidthDifferenceFromNominal.HasValue)
{
return Path.GetBoundingRectangle().GetValueOrDefault().Width;
}
return privateDictionary.NominalWidthX + WidthDifferenceFromNominal.Value;
Width = width;
}
}
}

View File

@@ -49,5 +49,18 @@
return 0;
}
public int GetGlyphIdByName(string characterName)
{
foreach (var keyValuePair in GlyphIdToStringIdAndName)
{
if (string.Equals(keyValuePair.Value.name, characterName, StringComparison.Ordinal))
{
return keyValuePair.Key;
}
}
return 0;
}
}
}

View File

@@ -30,5 +30,10 @@
{
throw new NotSupportedException("Cid Charsets do not support named glyphs.");
}
public int GetGlyphIdByName(string characterName)
{
return 0;
}
}
}

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System;
using System.Collections.Generic;
using System.Linq;
@@ -180,7 +181,7 @@
public static CompactFontFormatExpertCharset Value { get; } = new CompactFontFormatExpertCharset();
private readonly IReadOnlyDictionary<int, KeyValuePair<int, string>> characterIdToStringIdAndName;
private readonly IReadOnlyDictionary<int, KeyValuePair<int, string>> glyphIdToStringIdAndName;
public bool IsCidCharset { get; } = false;
@@ -193,22 +194,35 @@
furtherMap[i++] = pair;
}
characterIdToStringIdAndName = furtherMap;
glyphIdToStringIdAndName = furtherMap;
}
public string GetNameByGlyphId(int glyphId)
{
return characterIdToStringIdAndName[glyphId].Value;
return glyphIdToStringIdAndName[glyphId].Value;
}
public string GetNameByStringId(int stringId)
{
return characterIdToStringIdAndName.Single(x => x.Value.Key == stringId).Value.Value;
return glyphIdToStringIdAndName.Single(x => x.Value.Key == stringId).Value.Value;
}
public int GetStringIdByGlyphId(int glyphId)
{
return characterIdToStringIdAndName[glyphId].Key;
return glyphIdToStringIdAndName[glyphId].Key;
}
public int GetGlyphIdByName(string characterName)
{
foreach (var keyValuePair in glyphIdToStringIdAndName)
{
if (string.Equals(keyValuePair.Value.Value, characterName, StringComparison.Ordinal))
{
return keyValuePair.Key;
}
}
return 0;
}
}
}

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System;
using System.Collections.Generic;
using System.Linq;
@@ -101,7 +102,7 @@
public static CompactFontFormatExpertSubsetCharset Value { get; } = new CompactFontFormatExpertSubsetCharset();
private readonly IReadOnlyDictionary<int, KeyValuePair<int, string>> characterIdToStringIdAndName;
private readonly IReadOnlyDictionary<int, KeyValuePair<int, string>> glyphIdToStringIdAndName;
public bool IsCidCharset { get; } = false;
@@ -114,22 +115,35 @@
furtherMap[i++] = pair;
}
characterIdToStringIdAndName = furtherMap;
glyphIdToStringIdAndName = furtherMap;
}
public string GetNameByGlyphId(int glyphId)
{
return characterIdToStringIdAndName[glyphId].Value;
return glyphIdToStringIdAndName[glyphId].Value;
}
public string GetNameByStringId(int stringId)
{
return characterIdToStringIdAndName.Single(x => x.Value.Key == stringId).Value.Value;
return glyphIdToStringIdAndName.Single(x => x.Value.Key == stringId).Value.Value;
}
public int GetStringIdByGlyphId(int glyphId)
{
return characterIdToStringIdAndName[glyphId].Key;
return glyphIdToStringIdAndName[glyphId].Key;
}
public int GetGlyphIdByName(string characterName)
{
foreach (var keyValuePair in glyphIdToStringIdAndName)
{
if (string.Equals(keyValuePair.Value.Value, characterName, StringComparison.Ordinal))
{
return keyValuePair.Key;
}
}
return 0;
}
}
}

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System;
using System.Collections.Generic;
using System.Linq;
@@ -243,35 +244,48 @@
public static CompactFontFormatIsoAdobeCharset Value { get; } = new CompactFontFormatIsoAdobeCharset();
private readonly IReadOnlyDictionary<int, KeyValuePair<int, string>> characterIdToStringIdAndName;
private readonly IReadOnlyDictionary<int, KeyValuePair<int, string>> glyphIdToStringIdAndName;
public bool IsCidCharset { get; } = false;
private CompactFontFormatIsoAdobeCharset()
{
var furtherMap = new Dictionary<int, KeyValuePair<int, string>>();
var i = 0;
var gidToStringIdAndNameMap = new Dictionary<int, KeyValuePair<int, string>>();
var gid = 0;
foreach (var pair in StringIdToName)
{
furtherMap[i++] = pair;
gidToStringIdAndNameMap[gid++] = pair;
}
characterIdToStringIdAndName = furtherMap;
glyphIdToStringIdAndName = gidToStringIdAndNameMap;
}
public string GetNameByGlyphId(int glyphId)
{
return characterIdToStringIdAndName[glyphId].Value;
return glyphIdToStringIdAndName[glyphId].Value;
}
public string GetNameByStringId(int stringId)
{
return characterIdToStringIdAndName.Single(x => x.Value.Key == stringId).Value.Value;
return glyphIdToStringIdAndName.Single(x => x.Value.Key == stringId).Value.Value;
}
public int GetStringIdByGlyphId(int glyphId)
{
return characterIdToStringIdAndName[glyphId].Key;
return glyphIdToStringIdAndName[glyphId].Key;
}
public int GetGlyphIdByName(string characterName)
{
foreach (var keyValuePair in glyphIdToStringIdAndName)
{
if (string.Equals(keyValuePair.Value.Value, characterName, StringComparison.Ordinal))
{
return keyValuePair.Key;
}
}
return 0;
}
}
}

View File

@@ -9,5 +9,7 @@
string GetNameByStringId(int stringId);
int GetStringIdByGlyphId(int glyphId);
int GetGlyphIdByName(string characterName);
}
}

View File

@@ -31,17 +31,35 @@
public PdfRectangle? GetCharacterBoundingBox(string characterName)
{
if (characterName == ".notdef")
{
return new PdfRectangle(0, 0, 0, 0);
}
var defaultWidthX = GetDefaultWidthX(characterName);
var nominalWidthX = GetNominalWidthX(characterName);
var result = default(PdfRectangle?);
CharStrings.Match(x => throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported."),
x => { result = x.Generate(characterName).Path.GetBoundingRectangle(); });
var result = CharStrings.Match(x => throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported."),
x =>
{
var glyph = x.Generate(characterName, defaultWidthX, nominalWidthX);
var rectangle = glyph.Path.GetBoundingRectangle();
if (rectangle.HasValue)
{
return rectangle;
}
var defaultBoundingBox = TopDictionary.FontBoundingBox;
return new PdfRectangle(0, 0, glyph.Width.GetValueOrDefault(), defaultBoundingBox.Height);
});
return result;
}
protected virtual decimal GetDefaultWidthX(string characterName)
{
return PrivateDictionary.DefaultWidthX;
}
protected virtual decimal GetNominalWidthX(string characterName)
{
return PrivateDictionary.NominalWidthX;
}
}
internal class CompactFontFormatCidFont : CompactFontFormatFont
@@ -64,5 +82,42 @@
LocalSubroutines = localSubroutines;
FdSelect = fdSelect;
}
protected override decimal GetDefaultWidthX(string characterName)
{
if (!TryGetPrivateDictionaryForCharacter(characterName, out var dictionary))
{
return 1000;
}
return dictionary.DefaultWidthX;
}
protected override decimal GetNominalWidthX(string characterName)
{
if (!TryGetPrivateDictionaryForCharacter(characterName, out var dictionary))
{
return 0;
}
return dictionary.NominalWidthX;
}
private bool TryGetPrivateDictionaryForCharacter(string characterName, out CompactFontFormatPrivateDictionary dictionary)
{
dictionary = null;
var glyphId = Charset.GetGlyphIdByName(characterName);
var fd = FdSelect.GetFontDictionaryIndex(glyphId);
if (fd == -1)
{
return false;
}
dictionary = PrivateDictionaries[fd];
return true;
}
}
}

View File

@@ -62,7 +62,12 @@
public bool TryGetBoundingBox(int characterIdentifier, out PdfRectangle boundingBox)
{
boundingBox = new PdfRectangle(0, 0, 250, 0);
var font = GetFont();
var characterName = GetCharacterName(characterIdentifier);
boundingBox = font.GetCharacterBoundingBox(characterName) ?? new PdfRectangle(0, 0, 500, 0);
return true;
}

View File

@@ -322,7 +322,12 @@
public int GetFontDictionaryIndex(int glyphId)
{
throw new NotImplementedException();
if (glyphId < FontDictionaries.Count && glyphId >= 0)
{
return FontDictionaries[glyphId];
}
return 0;
}
}
@@ -343,7 +348,30 @@
public int GetFontDictionaryIndex(int glyphId)
{
throw new NotImplementedException();
for (var i = 0; i < Ranges.Count; ++i)
{
if (Ranges[i].First <= glyphId)
{
if (i + 1 < Ranges.Count)
{
if (Ranges[i + 1].First > glyphId)
{
return Ranges[i].FontDictionary;
}
}
else
{
if (Sentinel > glyphId)
{
return Ranges[i].FontDictionary;
}
return -1;
}
}
}
return 0;
}
internal struct Range3

View File

@@ -8,6 +8,8 @@ namespace UglyToad.PdfPig.Util
{
public abstract void Match(Action<A> first, Action<B> second);
public abstract TResult Match<TResult>(Func<A, TResult> first, Func<B, TResult> second);
private Union() { }
public static Case1 One(A item)
@@ -35,6 +37,12 @@ namespace UglyToad.PdfPig.Util
first(Item);
}
[DebuggerStepThrough]
public override TResult Match<TResult>(Func<A, TResult> first, Func<B, TResult> second)
{
return first(Item);
}
public override string ToString()
{
return Item?.ToString() ?? string.Empty;
@@ -56,6 +64,12 @@ namespace UglyToad.PdfPig.Util
second(Item);
}
[DebuggerStepThrough]
public override TResult Match<TResult>(Func<A, TResult> first, Func<B, TResult> second)
{
return second(Item);
}
public override string ToString()
{
return Item?.ToString() ?? string.Empty;