mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 11:44:51 +08:00
handle missing width and height correctly for compact font format fonts #75
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -30,5 +30,10 @@
|
||||
{
|
||||
throw new NotSupportedException("Cid Charsets do not support named glyphs.");
|
||||
}
|
||||
|
||||
public int GetGlyphIdByName(string characterName)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,5 +9,7 @@
|
||||
string GetNameByStringId(int stringId);
|
||||
|
||||
int GetStringIdByGlyphId(int glyphId);
|
||||
|
||||
int GetGlyphIdByName(string characterName);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user