Fix CID / CFF font glyphs and add tests

This commit is contained in:
BobLd
2024-01-15 18:23:15 +00:00
parent 5345422a87
commit 6103cf13dc
50 changed files with 453 additions and 119 deletions

View File

@@ -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>();
@@ -45,7 +45,7 @@
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.");
}
@@ -149,7 +149,7 @@
}
}
public class CommandSequence
public sealed class CommandSequence
{
public IReadOnlyList<float> Values { get; }
public IReadOnlyList<CommandIdentifier> CommandIdentifiers { get; }
@@ -157,7 +157,6 @@
/// <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;
@@ -218,7 +217,7 @@
/// 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.

View File

@@ -37,7 +37,7 @@
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)

View File

@@ -16,7 +16,7 @@
{
if (!codeToNameMap.TryGetValue(code, out var name))
{
return ".notdef";
return NotDefined;
}
return name;

View File

@@ -27,7 +27,7 @@
/// <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"/>.
@@ -50,6 +50,32 @@
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>
@@ -143,9 +169,17 @@
{
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; }
@@ -183,6 +217,26 @@
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;
@@ -199,5 +253,22 @@
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;
}
}
}

View File

@@ -65,16 +65,11 @@
/// <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;
}
}
}

View File

@@ -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)
{

View File

@@ -2,7 +2,7 @@
{
using System.Collections.Generic;
internal class CompactFontFormatFormat1Encoding : CompactFontFormatBuiltInEncoding
internal sealed class CompactFontFormatFormat1Encoding : CompactFontFormatBuiltInEncoding
{
public int NumberOfRanges { get; set; }
@@ -10,7 +10,7 @@
{
NumberOfRanges = numberOfRanges;
Add(0, 0, ".notdef");
Add(0, 0, NotDefined);
foreach (var value in values)
{

View File

@@ -2,7 +2,7 @@
{
using Core;
internal class CompactFontFormatTopLevelDictionary
internal sealed class CompactFontFormatTopLevelDictionary
{
public const int UnsetOffset = -1;
@@ -30,7 +30,7 @@
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; }
@@ -79,7 +79,7 @@
}
}
internal class CidFontOperators
internal sealed class CidFontOperators
{
public RegistryOrderingSupplement Ros { get; set; }
@@ -100,7 +100,7 @@
public string FontName { get; set; }
}
internal class RegistryOrderingSupplement
internal sealed class RegistryOrderingSupplement
{
public string Registry { get; set; }

View File

@@ -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>
@@ -24,7 +29,7 @@
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;
@@ -48,7 +53,7 @@
{
return CodeToName.ContainsKey(code);
}
/// <summary>
/// Get the character name corresponding to the given code.
/// </summary>
@@ -56,17 +61,16 @@
{
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)
{
@@ -90,7 +94,7 @@
NameToCode[name] = code;
}
}
/// <summary>
/// Get a known encoding instance with the given name.
/// </summary>

View File

@@ -11,7 +11,10 @@
/// </summary>
public 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;

View File

@@ -137,7 +137,7 @@
/// </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)

View File

@@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
internal class Type1CharstringDecryptedBytes
internal sealed class Type1CharstringDecryptedBytes
{
public IReadOnlyList<byte> Bytes { get; }
@@ -18,7 +18,7 @@
{
Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
Index = index;
Name = ".notdef";
Name = GlyphList.NotDefined;
Source = SourceType.Subroutine;
}

View File

@@ -1,13 +1,17 @@
namespace UglyToad.PdfPig.Tests.Integration.VisualVerification
{
using PdfPig.Core;
using SkiaSharp;
using System;
using System.IO;
using System.Linq;
using UglyToad.PdfPig.Tests.Integration.VisualVerification.SkiaHelpers;
using Xunit;
public class GenerateLetterGlyphImages
{
private const bool RenderGlyphRectangle = true;
private const string NonLatinAcrobatDistiller = "Single Page Non Latin - from acrobat distiller";
private const string SingleGoogleDrivePage = "Single Page Simple - from google drive";
private const string SinglePageFormattedType0Content = "Type0 Font";
@@ -23,10 +27,12 @@
private const string OutputPath = "ImagesGlyphs";
private const float Scale = 2f;
private const float Scale = 2.5f;
private static readonly SKMatrix ScaleMatrix = SKMatrix.CreateScale(Scale, Scale);
private static readonly SKPaint redPaint = new SKPaint() { Color = SKColors.Crimson, StrokeWidth = 1 };
public GenerateLetterGlyphImages()
{
if (!Directory.Exists(OutputPath))
@@ -56,17 +62,28 @@
document.AddPageFactory<SKPicture, SkiaGlyphPageFactory>();
var page = document.GetPage(pageNo);
var size = new SKSizeI((int)(page.Width * Scale), (int)(page.Height * Scale));
using (var picture = document.GetPage<SKPicture>(pageNo))
using (var image = SKImage.FromPicture(picture, size, ScaleMatrix))
using (var bmp = SKBitmap.FromImage(image))
using (var canvas = new SKCanvas(bmp))
{
Assert.NotNull(picture);
if (RenderGlyphRectangle)
{
foreach (var letter in page.Letters)
{
DrawRectangle(letter.GlyphRectangle, canvas, redPaint, size.Height, Scale);
}
}
var imageName = $"{file}_{pageNo}.png";
var savePath = Path.Combine(OutputPath, imageName);
using (var fs = new FileStream(savePath, FileMode.Create))
using (var image = SKImage.FromPicture(picture, new SKSizeI((int)(page.Width * Scale), (int)(page.Height * Scale)), ScaleMatrix))
using (SKData d = image.Encode(SKEncodedImageFormat.Png, 100))
using (var d = bmp.Encode(SKEncodedImageFormat.Png, 100))
{
d.SaveTo(fs);
}
@@ -74,6 +91,174 @@
}
}
private static void DrawRectangle(PdfRectangle rectangle, SKCanvas graphics, SKPaint pen, int imageHeight, double scale)
{
int GetY(PdfPoint p)
{
return imageHeight - (int)(p.Y * scale);
}
SKPoint GetPoint(PdfPoint p)
{
return new SKPoint((int)(p.X * scale), GetY(p));
}
graphics.DrawLine(GetPoint(rectangle.BottomLeft), GetPoint(rectangle.BottomRight), pen);
graphics.DrawLine(GetPoint(rectangle.BottomRight), GetPoint(rectangle.TopRight), pen);
graphics.DrawLine(GetPoint(rectangle.TopRight), GetPoint(rectangle.TopLeft), pen);
graphics.DrawLine(GetPoint(rectangle.TopLeft), GetPoint(rectangle.BottomLeft), pen);
}
// veraPDF_Issue1010_x -> https://github.com/veraPDF/veraPDF-library/issues/1010
[Fact(Skip = "Text is in annotations")]
public void veraPDF_Issue1010_1()
{
Run("FontMatrix-1");
}
[Fact(Skip = "Skipping for the moment")]
public void veraPDF_Issue1010_2()
{
Run("FontMatrix-otf");
}
[Fact(Skip = "Skipping for the moment")]
public void veraPDF_Issue1010_3()
{
Run("FontMatrix-raw");
}
[Fact]
public void veraPDF_Issue1010_4()
{
Run("felltypes-test");
}
[Fact(Skip = "Skipping for the moment")]
public void veraPDF_Issue1010_5()
{
Run("FontMatrix-otf-bad-hmtx");
}
[Fact(Skip = "Text is in annotations")]
public void veraPDF_Issue1010_6()
{
Run("FontMatrix-concat");
}
[Fact]
public void EmbeddedCidFont_1()
{
Run("GHOSTSCRIPT-696547-0.zip-7");
}
[Fact]
public void EmbeddedCidFont_2()
{
Run("GHOSTSCRIPT-696547-0.zip-9");
}
[Fact]
public void EmbeddedCidFont_Music_1()
{
Run("GHOSTSCRIPT-696171-0");
}
[Fact]
public void pdf995_1()
{
Run("GHOSTSCRIPT-699035-0");
}
[Fact]
public void pdf995_3()
{
Run("GHOSTSCRIPT-699035-0", 3);
}
[Fact]
public void EmbeddedType1Cid_1()
{
Run("GHOSTSCRIPT-697507-0");
}
[Fact(Skip = "Glyphs cannot be rendered")]
public void EmbeddedType1Cid_MatrixIssue()
{
// It seems it's correct that the glyphs cannot be rendered
// Leaving it there just in case
Run("GHOSTSCRIPT-698168-0");
}
[Fact]
public void EmbeddedType1Cid_2()
{
Run("GHOSTSCRIPT-698721-0.zip-6");
}
[Fact]
public void EmbeddedType1Cid_3()
{
Run("GHOSTSCRIPT-699554-0.zip-4");
}
[Fact]
public void EmbeddedType1Cid_4()
{
Run("GHOSTSCRIPT-700931-0.7z-5");
}
[Fact]
public void EmbeddedType1_PatternColor_1()
{
Run("GHOSTSCRIPT-698721-1");
}
[Fact]
public void RepresentativeWindEnergyDeals()
{
// Colors look wrong but they are correct as we do not support ICC profile color space.
// The colors are the same as when the document is opened in pdf.js (Firefox).
Run("GHOSTSCRIPT-702013-1");
}
[Fact]
public void Psion()
{
Run("GHOSTSCRIPT-700236-1");
}
[Fact]
public void RabobankWestland()
{
Run("GHOSTSCRIPT-700125-1");
}
[Fact]
public void MammaMia()
{
Run("GHOSTSCRIPT-699375-5");
}
[Fact]
public void InstallingMuAndPygameZero()
{
// Still an issue with some of the glyphs
Run("GHOSTSCRIPT-700139-0");
}
[Fact]
public void FolhaDeLondrina()
{
Run("GHOSTSCRIPT-699488-0");
}
[Fact]
public void LithgowHighSchool()
{
Run("GHOSTSCRIPT-700370-2");
}
[Fact]
public void TIKA_1552_0_4()
{
@@ -83,7 +268,7 @@
[Fact]
public void TIKA_1552_0_3()
{
Run("TIKA-1552-0",3);
Run("TIKA-1552-0", 3);
}
[Fact]
@@ -119,6 +304,7 @@
[Fact]
public void SinglePageSimpleFromInkscape()
{
// Issue with Glyph related to Bézier curves in PdfPig
Run(SingleInkscapePage);
}

View File

@@ -8,19 +8,20 @@
internal static class SkiaExtensions
{
public static SKMatrix ToSkMatrix(this TransformationMatrix transformationMatrix)
public static SKMatrix ToSKMatrix(this TransformationMatrix transformationMatrix)
{
return new SKMatrix((float)transformationMatrix.A, (float)transformationMatrix.C, (float)transformationMatrix.E,
(float)transformationMatrix.B, (float)transformationMatrix.D, (float)transformationMatrix.F,
0, 0, 1);
}
public static SKColor ToSKColor(this IColor? pdfColor, decimal alpha)
public static SKColor ToSKColor(this IColor pdfColor, decimal alpha)
{
var color = SKColors.Black;
if (pdfColor != null)
if (pdfColor != null && pdfColor is not PatternColor)
{
var (r, g, b) = pdfColor.ToRGBValues();
color = new SKColor(Convert.ToByte(r * 255), Convert.ToByte(g * 255), Convert.ToByte(b * 255));
}

View File

@@ -103,9 +103,9 @@
TransformationMatrix textMatrix,
TransformationMatrix transformationMatrix)
{
var transformMatrix = renderingMatrix.ToSkMatrix()
.PostConcat(textMatrix.ToSkMatrix())
.PostConcat(transformationMatrix.ToSkMatrix())
var transformMatrix = renderingMatrix.ToSKMatrix()
.PostConcat(textMatrix.ToSKMatrix())
.PostConcat(transformationMatrix.ToSKMatrix())
.PostConcat(yAxisFlipMatrix);
var style = textRenderingMode.ToSKPaintStyle();

View File

@@ -54,6 +54,8 @@
PdfVector GetDisplacementVector(int characterIdentifier);
TransformationMatrix GetFontMatrix(int characterIdentifier);
/// <summary>
/// Returns the glyph path for the given character code.
/// </summary>

View File

@@ -19,10 +19,15 @@
bool TryGetBoundingAdvancedWidth(int characterIdentifier, out double width);
bool TryGetPath(int characterName, out IReadOnlyList<PdfSubpath> path);
bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
int GetFontMatrixMultiplier();
/// <summary>
/// Try to get the font matrix if available.
/// </summary>
bool TryGetFontMatrix(int characterCode, out TransformationMatrix? matrix);
}
}

View File

@@ -3,9 +3,10 @@
using System;
using System.Collections.Generic;
using Core;
using Fonts;
using Fonts.CompactFontFormat;
internal class PdfCidCompactFontFormatFont : ICidFontProgram
internal sealed class PdfCidCompactFontFormatFont : ICidFontProgram
{
private readonly CompactFontFormatFontCollection fontCollection;
@@ -51,13 +52,13 @@
var font = GetFont();
if (font.Encoding == null)
var characterName = GetCharacterName(characterIdentifier);
if (string.Equals(characterName, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
return false;
}
var characterName = GetCharacterName(characterIdentifier);
boundingBox = font.GetCharacterBoundingBox(characterName) ?? new PdfRectangle(0, 0, 500, 0);
return true;
@@ -83,16 +84,26 @@
return 1000;
}
public bool TryGetFontMatrix(int characterCode, out TransformationMatrix? matrix)
{
var font = GetFont();
var name = font.GetCharacterName(characterCode, true);
if (name == null)
{
matrix = null;
return false;
}
matrix = font.GetFontMatrix(name);
return matrix.HasValue;
}
public string GetCharacterName(int characterCode)
{
var font = GetFont();
if (font.Encoding != null)
{
return font.Encoding.GetName(characterCode);
}
var name = font.GetCharacterName(characterCode, true);
return ".notdef";
return name ?? GlyphList.NotDefined;
}
private CompactFontFormatFont GetFont()
@@ -109,17 +120,17 @@
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
{
path = EmptyArray<PdfSubpath>.Instance;
path = null;
var font = GetFont();
if (font.Encoding == null)
var characterName = GetCharacterName(characterCode);
if (string.Equals(characterName, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
return false;
}
var characterName = GetCharacterName(characterCode);
if (font.TryGetPath(characterName, out path))
{
return true;

View File

@@ -6,7 +6,7 @@
using Fonts.TrueType;
using Fonts.TrueType.Tables;
internal class PdfCidTrueTypeFont : ICidFontProgram
internal sealed class PdfCidTrueTypeFont : ICidFontProgram
{
private readonly TrueTypeFont font;
@@ -35,6 +35,13 @@
public int GetFontMatrixMultiplier() => font.GetUnitsPerEm();
public bool TryGetFontMatrix(int characterCode, out TransformationMatrix? matrix)
{
// We don't have a matrix here
matrix = null;
return false;
}
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => font.TryGetPath(characterCode, out path);
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)

View File

@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
using Geometry;
using Tokens;
@@ -11,7 +12,7 @@
/// Type 0 CID fonts contain glyph descriptions based on the
/// Adobe Type 1 font format.
/// </summary>
internal class Type0CidFont : ICidFont
internal sealed class Type0CidFont : ICidFont
{
private readonly ICidFontProgram fontProgram;
private readonly VerticalWritingMetrics verticalWritingMetrics;
@@ -26,8 +27,8 @@
public CharacterIdentifierSystemInfo SystemInfo { get; }
public FontDetails Details => fontProgram?.Details ?? Descriptor?.ToDetails(BaseFont?.Data)
?? FontDetails.GetDefault(BaseFont?.Data);
public FontDetails Details => fontProgram?.Details ?? Descriptor?.ToDetails(BaseFont?.Data)
?? FontDetails.GetDefault(BaseFont?.Data);
public TransformationMatrix FontMatrix { get; }
@@ -37,10 +38,13 @@
public IReadOnlyDictionary<int, double> Widths { get; }
public Type0CidFont(ICidFontProgram fontProgram, NameToken type, NameToken subType, NameToken baseFont,
public Type0CidFont(ICidFontProgram fontProgram,
NameToken type,
NameToken subType,
NameToken baseFont,
CharacterIdentifierSystemInfo systemInfo,
FontDescriptor descriptor,
VerticalWritingMetrics verticalWritingMetrics,
FontDescriptor descriptor,
VerticalWritingMetrics verticalWritingMetrics,
IReadOnlyDictionary<int, double> widths,
double? defaultWidth)
{
@@ -80,7 +84,7 @@
{
return defaultWidth.Value;
}
if (Descriptor == null)
{
return 1000;
@@ -132,6 +136,16 @@
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
}
public TransformationMatrix GetFontMatrix(int characterIdentifier)
{
if (fontProgram == null)
{
return FontMatrix;
}
return fontProgram.TryGetFontMatrix(characterIdentifier, out var m) ? m.Value : FontMatrix;
}
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
{
path = null;
@@ -142,7 +156,7 @@
return fontProgram.TryGetPath(characterCode, out path);
}
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
{
path = null;
@@ -156,12 +170,36 @@
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
{
return TryGetPath(characterCode, out path);
path = null;
if (fontProgram == null)
{
return false;
}
if (fontProgram.TryGetPath(characterCode, out path))
{
path = GetFontMatrix(characterCode).Transform(path).ToArray();
return true;
}
return false;
}
public bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
{
return TryGetPath(characterCode, characterCodeToGlyphId, out path);
path = null;
if (fontProgram == null)
{
return false;
}
if (fontProgram.TryGetPath(characterCode, characterCodeToGlyphId, out path))
{
path = GetFontMatrix(characterCode).Transform(path).ToArray();
return true;
}
return false;
}
}
}

View File

@@ -12,7 +12,7 @@
/// Type 2 CID fonts contains glyph descriptions based on
/// the TrueType font format.
/// </summary>
internal class Type2CidFont : ICidFont
internal sealed class Type2CidFont : ICidFont
{
private readonly ICidFontProgram fontProgram;
private readonly VerticalWritingMetrics verticalWritingMetrics;
@@ -35,10 +35,14 @@
public FontDescriptor Descriptor { get; }
public FontDetails Details => fontProgram?.Details ?? Descriptor?.ToDetails(BaseFont?.Data)
?? FontDetails.GetDefault(BaseFont?.Data);
?? FontDetails.GetDefault(BaseFont?.Data);
public Type2CidFont(NameToken type, NameToken subType, NameToken baseFont, CharacterIdentifierSystemInfo systemInfo,
FontDescriptor descriptor, ICidFontProgram fontProgram,
public Type2CidFont(NameToken type,
NameToken subType,
NameToken baseFont,
CharacterIdentifierSystemInfo systemInfo,
FontDescriptor descriptor,
ICidFontProgram fontProgram,
VerticalWritingMetrics verticalWritingMetrics,
IReadOnlyDictionary<int, double> widths,
double? defaultWidth,
@@ -71,6 +75,7 @@
{
return width;
}
// TODO: Read the font width from the font program.
return GetWidthFromDictionary(characterIdentifier);
}
@@ -117,6 +122,11 @@
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
}
public TransformationMatrix GetFontMatrix(int characterIdentifier)
{
return FontMatrix;
}
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => TryGetPath(characterCode, cidToGid.GetGlyphIndex, out path);
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
@@ -132,18 +142,24 @@
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
{
if (!TryGetPath(characterCode, out path))
{
return false;
}
path = FontMatrix.Transform(path).ToList();
return true;
return TryGetNormalisedPath(characterCode, cidToGid.GetGlyphIndex, out path);
}
public bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
{
throw new NotImplementedException();
path = null;
if (fontProgram == null)
{
return false;
}
if (fontProgram.TryGetPath(characterCode, characterCodeToGlyphId, out path))
{
path = GetFontMatrix(characterCode).Transform(path).ToArray();
return true;
}
return false;
}
}
}

View File

@@ -13,7 +13,7 @@
/// <summary>
/// Defines glyphs using a CIDFont
/// </summary>
internal class Type0Font : IFont, IVerticalWritingSupported
internal sealed class Type0Font : IFont, IVerticalWritingSupported
{
private readonly CMap ucs2CMap;
// ReSharper disable once NotAccessedField.Local
@@ -117,17 +117,18 @@
return cached;
}
var matrix = GetFontMatrix();
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
boundingBox = matrix.Transform(boundingBox);
var characterIdentifier = CMap.ConvertToCid(characterCode);
// Get the bounding box in glyph space
var boundingBox = CidFont.GetBoundingBox(characterIdentifier);
boundingBox = CidFont.GetFontMatrix(characterIdentifier).Transform(boundingBox);
var width = CidFont.GetWidthFromFont(characterIdentifier);
var advanceWidth = matrix.TransformX(width);
var advanceWidth = GetFontMatrix().TransformX(width);
// BobLD: Not sure why we don't need CidFont.GetFontMatrix(characterCode)
// Might be related to https://github.com/veraPDF/veraPDF-library/issues/1010
var result = new CharacterBoundingBox(boundingBox, advanceWidth);
@@ -136,13 +137,6 @@
return result;
}
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
{
var characterIdentifier = CMap.ConvertToCid(characterCode);
return CidFont.GetBoundingBox(characterIdentifier);
}
public TransformationMatrix GetFontMatrix()
{
return CidFont.FontMatrix;
@@ -165,13 +159,17 @@
/// <inheritdoc/>
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
{
return CidFont.TryGetPath(characterCode, out path);
var characterIdentifier = CMap.ConvertToCid(characterCode);
return CidFont.TryGetPath(characterIdentifier, out path);
}
/// <inheritdoc/>
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
{
return CidFont.TryGetNormalisedPath(characterCode, out path);
var characterIdentifier = CMap.ConvertToCid(characterCode);
return CidFont.TryGetNormalisedPath(characterIdentifier, out path);
}
}
}

View File

@@ -12,7 +12,7 @@
using Tokens;
using Util.JetBrains.Annotations;
internal class TrueTypeSimpleFont : IFont
internal sealed class TrueTypeSimpleFont : IFont
{
private static readonly TransformationMatrix DefaultTransformation =
TransformationMatrix.FromValues(1 / 1000.0, 0, 0, 1 / 1000.0, 0, 0);
@@ -230,7 +230,7 @@
return null;
}
if (string.Equals(name, ".notdef", StringComparison.OrdinalIgnoreCase))
if (string.Equals(name, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
return 0;
}
@@ -328,7 +328,7 @@
{
if (font == null)
{
path = EmptyArray<PdfSubpath>.Instance;
path = null;
return false;
}
@@ -343,7 +343,7 @@
return false;
}
path = GetFontMatrix().Transform(path).ToList();
path = GetFontMatrix().Transform(path).ToArray();
return true;
}
}

View File

@@ -13,7 +13,7 @@
/// <summary>
/// Some TrueType fonts use both the Standard 14 descriptor and the TrueType font from disk.
/// </summary>
internal class TrueTypeStandard14FallbackSimpleFont : IFont
internal sealed class TrueTypeStandard14FallbackSimpleFont : IFont
{
private static readonly TransformationMatrix DefaultTransformation =
TransformationMatrix.FromValues(1 / 1000.0, 0, 0, 1 / 1000.0, 0, 0);
@@ -152,7 +152,7 @@
return false;
}
path = GetFontMatrix().Transform(path).ToList();
path = GetFontMatrix().Transform(path).ToArray();
return true;
}

View File

@@ -15,7 +15,7 @@
/// <summary>
/// A font based on the Adobe Type 1 font format.
/// </summary>
internal class Type1FontSimple : IFont
internal sealed class Type1FontSimple : IFont
{
private static readonly TransformationMatrix DefaultTransformationMatrix = TransformationMatrix.FromValues(0.001, 0, 0, 0.001, 0, 0);
@@ -197,7 +197,7 @@
}
else
{
characterName = cffFont.GetCharacterName(characterCode);
characterName = cffFont.GetCharacterName(characterCode, false);
}
rect = first.GetCharacterBoundingBox(characterName);
@@ -247,7 +247,7 @@
}
else
{
characterName = cffFont.GetCharacterName(characterCode);
characterName = cffFont.GetCharacterName(characterCode, false);
}
tempPath = first.GetCharacterPath(characterName);
@@ -267,7 +267,7 @@
{
if (TryGetPath(characterCode, out path))
{
path = fontMatrix.Transform(path).ToList();
path = fontMatrix.Transform(path).ToArray();
return true;
}
return false;

View File

@@ -13,7 +13,7 @@ namespace UglyToad.PdfPig.PdfFonts.Simple
/// <summary>
/// A font using one of the Adobe Standard 14 fonts. Can use a custom encoding.
/// </summary>
internal class Type1Standard14Font : IFont
internal sealed class Type1Standard14Font : IFont
{
private readonly AdobeFontMetrics standardFontMetrics;
private readonly Encoding encoding;
@@ -49,7 +49,7 @@ namespace UglyToad.PdfPig.PdfFonts.Simple
public bool TryGetUnicode(int characterCode, out string value)
{
var name = encoding.GetName(characterCode);
if (string.Equals(name, ".notdef", StringComparison.OrdinalIgnoreCase))
if (string.Equals(name, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
value = null;
return false;

View File

@@ -10,7 +10,7 @@
using PdfPig.Fonts.Encodings;
using Tokens;
internal class Standard14WritingFont : IWritingFont
internal sealed class Standard14WritingFont : IWritingFont
{
private readonly AdobeFontMetrics metrics;
@@ -25,7 +25,6 @@
public bool TryGetBoundingBox(char character, out PdfRectangle boundingBox)
{
boundingBox = default(PdfRectangle);
int code = CodeMapIfUnicode(character);
@@ -118,7 +117,7 @@
private int UnicodeToSymbolCode(char character)
{
var name = GlyphList.AdobeGlyphList.UnicodeCodePointToName(character);
if (string.Equals(name, ".notdef", StringComparison.OrdinalIgnoreCase))
if (string.Equals(name, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
return -1;
}
@@ -134,7 +133,7 @@
private int UnicodeToZapfDingbats(char character)
{
var name = GlyphList.ZapfDingbats.UnicodeCodePointToName(character);
if (string.Equals(name, ".notdef", StringComparison.OrdinalIgnoreCase))
if (string.Equals(name, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
Debug.WriteLine($"Failed to find Unicode character '{character}' (0x{(int)character:X}).");
return -1;
@@ -147,13 +146,12 @@
Debug.WriteLine($"Found Unicode point '{character}' (0x{(int)character:X}) but glphy name '{name}' not found in font '{metrics.FontName}' (font specific encoding: ZapfDingbats).");
}
return code;
}
private int UnicodeToStandardEncoding(char character)
{
var name = GlyphList.AdobeGlyphList.UnicodeCodePointToName(character);
if (string.Equals(name, ".notdef", StringComparison.OrdinalIgnoreCase))
if (string.Equals(name, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
{
Debug.WriteLine($"Failed to find Unicode character '{character}' (0x{(int)character:X}).");
return -1;
@@ -167,7 +165,7 @@
code = standardEncoding.GetCode(nameCapitalisedChange);
if (code == -1)
{
Debug.WriteLine($"Found Unicode point '{character}' (0x{(int)character:X}) but glphy name '{name}' not found in font '{metrics.FontName}' (StandardEncoding).");
Debug.WriteLine($"Found Unicode point '{character}' (0x{(int)character:X}) but glyph name '{name}' not found in font '{metrics.FontName}' (StandardEncoding).");
}
}
return code;